org.springsource.loaded.MethodCopier.java Source code

Java tutorial

Introduction

Here is the source code for org.springsource.loaded.MethodCopier.java

Source

/*
 * Copyright 2010-2012 VMware and contributors
 *
 * 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.springsource.loaded;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.springsource.loaded.Utils.ReturnType;

/**
 * 
 * @author Andy Clement
 * @since 0.5.0
 */
class MethodCopier extends MethodVisitor implements Constants {

    private boolean isInterface;
    private String descriptor;
    private TypeDescriptor typeDescriptor;
    private String classname;
    private String suffix;
    private boolean hasFieldsRequiringAccessors;

    public MethodCopier(MethodVisitor mv, boolean isInterface, String descriptor, TypeDescriptor typeDescriptor,
            String classname, String suffix) {
        super(ASM5, mv);
        this.isInterface = isInterface;
        this.descriptor = descriptor;
        this.typeDescriptor = typeDescriptor;
        this.classname = classname;
        this.suffix = suffix;
        this.hasFieldsRequiringAccessors = this.typeDescriptor.getFieldsRequiringAccessors().length != 0;
    }

    @Override
    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
        // Rename 'this' to 'thiz' in executor otherwise Eclipse debugger will fail (static method with 'this')
        if (index == 0 && name.equals("this")) {
            super.visitLocalVariable("thiz", desc, signature, start, end, index);
        } else {
            super.visitLocalVariable(name, desc, signature, start, end, index);
        }
    }

    private FieldMember findFieldIfRequiresAccessorUsage(String owner, String name) {
        FieldMember[] fms = this.typeDescriptor.getFieldsRequiringAccessors();
        for (FieldMember fm : fms) {
            if (fm.getName().equals(name) && (owner.equals(classname) || isOneOfOurSupertypes(owner))) { // && fm.getDeclaringTypeName().equals(owner)) {
                // possibly a match - testcase scenario:
                // 'owner=prot/SubThree' - this is what the FIELD instruction is working on
                // 'dfm=prot/Three' - this is the type that declared the field
                // 'classname=prot/SubThree' - this is the type we are currently operating on

                // in our other case though (with JDK Proxies)
                // owner=java/lang/reflect/Proxy
                // dfm=java/lang/reflect/Proxy
                // classname=$Proxy6
                // (this is the funky InvocationHandler field called 'h' in Proxy)
                return fm;
            }
        }
        return null;
    }

    /**
     * Determine if the supplied type is a supertype of the current type we are modifying. This is used to determine if the owner we
     * have discovered for a field is one of our supertypes (and so, if it is protected, whether it is something that needs
     * redirecting through an accessor).
     * 
     * @param type the type which may be one of this types supertypes
     * @return true if it is a supertype
     */
    private boolean isOneOfOurSupertypes(String type) {
        String stypeName = typeDescriptor.getSupertypeName();
        while (stypeName != null) {
            // TODO [bug] should stop at the first one that has a field in it? and check the field is protected, yada yada yada
            if (stypeName.equals(type)) {
                return true;
            }
            stypeName = typeDescriptor.getTypeRegistry().getDescriptorFor(stypeName).getSupertypeName();
        }
        return false;
    }

    private TypeDescriptor getType(String type) {
        TypeDescriptor typeDescriptor = this.typeDescriptor.getTypeRegistry().getDescriptorFor(type);
        return typeDescriptor;
    }

    @Override
    public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
        if (hasFieldsRequiringAccessors) {
            // Check if this field reference needs redirecting to an accessor
            FieldMember fm = findFieldIfRequiresAccessorUsage(owner, name);
            if (fm != null) {
                switch (opcode) {
                case GETFIELD:
                    mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldGetterName(name),
                            "()" + desc, false);
                    return;
                case PUTFIELD:
                    mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldSetterName(name),
                            "(" + desc + ")V", false);
                    return;
                case GETSTATIC:
                    mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldGetterName(name),
                            "()" + desc, false);
                    return;
                case PUTSTATIC:
                    mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldSetterName(name),
                            "(" + desc + ")V", false);
                    return;
                }
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }

    // TODO maybe do something if 'itf==true'
    @Override
    public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc,
            boolean itf) {
        // Is it a private method call?
        // TODO r$ check here because we use invokespecial to avoid virtual dispatch on field changes...
        if (opcode == INVOKESPECIAL && name.charAt(0) != '<' && !name.startsWith("r$")) {
            if (owner.equals(classname)) {
                // private method call
                // leaving the invokespecial alone will cause a verify error
                String descriptor = Utils.insertExtraParameter(owner, desc);
                super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor,
                        false);
                return;
            } else {
                // super call
                // TODO Check if this is true: we can just call the catcher directly if there was one, there is no need
                // for a superdispatcher

                // Only need to redirect to the superdispatcher if it was a protected method
                TypeDescriptor supertypeDescriptor = getType(owner);
                MethodMember target = supertypeDescriptor.getByNameAndDescriptor(name + desc);
                if (target != null && target.isProtected()) {
                    // A null target means that method is not in the supertype, so didn't get a superdispatcher
                    super.visitMethodInsn(INVOKESPECIAL, classname, name + methodSuffixSuperDispatcher, desc,
                            false);
                } else {
                    super.visitMethodInsn(opcode, owner, name, desc, itf);
                }
                return;
            }
        }
        // Might be a private static method
        boolean done = false;
        if (opcode == INVOKESTATIC) {
            MethodMember mm = typeDescriptor.getByDescriptor(name, desc);
            if (mm != null && mm.isPrivate()) {
                super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, desc, false);
                done = true;
            }
        }
        if (!done) {
            super.visitMethodInsn(opcode, owner, name, desc, itf);
        }
    }

    @Override
    public void visitEnd() {
        if (isInterface) {
            // Create 'dummy methods' for an interface implementation
            createDummyMethodBody();
            super.visitEnd();
        }
    }

    private void createDummyMethodBody() {
        ReturnType returnType = Utils.getReturnTypeDescriptor(descriptor);
        int descriptorSize = Utils.getSize(descriptor);
        if (returnType.isVoid()) {
            super.visitInsn(RETURN);
            super.visitMaxs(1, descriptorSize);
        } else if (returnType.isPrimitive()) {
            super.visitLdcInsn(0);
            switch (returnType.descriptor.charAt(0)) {
            case 'B':
            case 'C':
            case 'I':
            case 'S':
            case 'Z':
                super.visitInsn(IRETURN);
                super.visitMaxs(2, descriptorSize);
                break;
            case 'D':
                super.visitInsn(DRETURN);
                super.visitMaxs(3, descriptorSize);
                break;
            case 'F':
                super.visitInsn(FRETURN);
                super.visitMaxs(2, descriptorSize);
                break;
            case 'J':
                super.visitInsn(LRETURN);
                super.visitMaxs(3, descriptorSize);
                break;
            default:
                throw new IllegalStateException(returnType.descriptor);
            }
        } else {
            // reference type
            super.visitInsn(ACONST_NULL);
            super.visitInsn(ARETURN);
            super.visitMaxs(1, descriptorSize);
        }
    }
}