All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.imadz.bcel.JavaAnonymousInnerClass Maven / Gradle / Ivy

Go to download

Lifecycle AOP Framework. Enabling describe enhanced State Diagram with Java language, and processing relational Lifecycle constraints(including: state validations, transitions validations), concurrency, lifecycle callbacks, lifecycle events implicitely with Lifecycle Engine. This engine can be used by class load time (Java Instrumentation) or compile time (Lifecycle-StaticWeaver-maven-plugin).

There is a newer version: 0.9.13
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright (c) 2013-2020 Madz. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can
 * obtain a copy of the License at
 * https://raw.github.com/zhongdj/Lifecycle/master/License.txt
 * . See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 * 
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 * 
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package net.imadz.bcel;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.imadz.org.apache.bcel.Constants;
import net.imadz.org.apache.bcel.classfile.Field;
import net.imadz.org.apache.bcel.classfile.InnerClass;
import net.imadz.org.apache.bcel.classfile.InnerClasses;
import net.imadz.org.apache.bcel.classfile.JavaClass;
import net.imadz.org.apache.bcel.classfile.LocalVariableTable;
import net.imadz.org.apache.bcel.classfile.Method;
import net.imadz.org.apache.bcel.classfile.Signature;
import net.imadz.org.apache.bcel.classfile.Unknown;
import net.imadz.org.apache.bcel.generic.ClassGen;
import net.imadz.org.apache.bcel.generic.ConstantPoolGen;
import net.imadz.org.apache.bcel.generic.FieldGen;
import net.imadz.org.apache.bcel.generic.Instruction;
import net.imadz.org.apache.bcel.generic.InstructionConstants;
import net.imadz.org.apache.bcel.generic.InstructionFactory;
import net.imadz.org.apache.bcel.generic.InstructionList;
import net.imadz.org.apache.bcel.generic.LocalVariableInstruction;
import net.imadz.org.apache.bcel.generic.MethodGen;
import net.imadz.org.apache.bcel.generic.ObjectType;
import net.imadz.org.apache.bcel.generic.ReturnInstruction;
import net.imadz.org.apache.bcel.generic.Type;
import net.imadz.org.apache.bcel.util.ClassPath;
import net.imadz.org.apache.bcel.util.SyntheticRepository;
import net.imadz.org.apache.bcel.verifier.statics.StringRepresentation;

public class JavaAnonymousInnerClass {

    private static final Logger logger = Logger.getLogger("Lifecycle Framework Byte Code Transformer");
    private static final String POSTFIX = "$Impl";
    private final String outerClassName;
    private final String enclosingMethodName;
    private final Type[] enclosingMethodArguments;
    private final int innerClassSeq;
    private final String superClass;
    private final Type[] superClassTypeParameters;
    private final String interfaceClass;
    private final Type[] interfaceClassTypeParameters;
    private final String thisClassName;
    private final String sourceFile;
    private final String location;

    public JavaAnonymousInnerClass(String outerClassName, String enclosingMethodName, Type[] enclosingMethodArguments, int innerClassSeq, String superClass,
            String interfaceClass) {
        this(outerClassName, enclosingMethodName, enclosingMethodArguments, innerClassSeq, superClass, new Type[0], interfaceClass, new Type[0], null);
    }

    public JavaAnonymousInnerClass(String outerClassName, String enclosingMethodName, Type[] enclosingMethodArguments, int innerClassSeq, String superClass,
            Type[] superClassTypeParameters, String interfaceClass, Type[] interfaceClassTypeParameters, String location) {
        super();
        this.outerClassName = outerClassName;
        this.enclosingMethodName = enclosingMethodName;
        this.enclosingMethodArguments = enclosingMethodArguments;
        this.innerClassSeq = innerClassSeq;
        this.superClass = superClass;
        this.interfaceClass = interfaceClass;
        this.thisClassName = this.outerClassName + "$" + this.innerClassSeq;
        this.sourceFile = parseSourceFileName();
        this.superClassTypeParameters = superClassTypeParameters;
        this.interfaceClassTypeParameters = interfaceClassTypeParameters;
        this.location = location;
    }

    private String parseSourceFileName() {
        int first$ = this.outerClassName.indexOf("$");
        if ( -1 < first$ ) {
            int lastDot = this.outerClassName.substring(0, first$).lastIndexOf(".");
            return this.outerClassName.substring(lastDot + 1, first$) + ".java";
        } else {
            int lastDot = this.outerClassName.lastIndexOf(".");
            return this.outerClassName.substring(lastDot + 1) + ".java";
        }
    }

    public ClassGen doGenerate() throws Throwable {
        final ClassGen cgen = new ClassGen(this.thisClassName, this.superClass, this.sourceFile, Constants.ACC_SYNCHRONIZED, new String[0]);
        // generate interface
        if ( null != this.interfaceClass && !this.interfaceClass.isEmpty() ) {
            cgen.addInterface(this.interfaceClass);
        }
        {// generate signature attribute
            doGenerateSignatureAttribute(cgen);
        }
        {// generate enclosing method attribute
            doGenerateEnclosingMethodAttribute(cgen);
        }
        {// generate inner classes attribute
            generateInnerClassesAttribute(cgen);
        }
        {// generate fields
            doGenerateFields(cgen);
        }
        {// generate constructor
            doGenerateConstructor(cgen);
        }
        {// generate methods
            doGenerateMethods(cgen);
        }
        // Dump
        StringRepresentation visitor = new StringRepresentation(cgen.getJavaClass());
        if ( logger.isLoggable(Level.FINE) ) {
            logger.fine(visitor.toString());
        }
        try {
            String classFilePath = null;
            if ( '\\' == File.separatorChar ) {
                classFilePath = thisClassName.replaceAll("\\.", "\\\\");
            } else {
                classFilePath = thisClassName.replaceAll("\\.", File.separator);
            }
            final String path = this.location + classFilePath + ".class";
            cgen.getJavaClass().dump(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return cgen;
    }

    private void generateInnerClassesAttribute(final ClassGen cgen) {
        ConstantPoolGen constantPool = cgen.getConstantPool();
        int innerClasses_index = constantPool.addUtf8("InnerClasses");
        final InnerClasses inner = new InnerClasses(innerClasses_index, 10, new InnerClass[] { new InnerClass(constantPool.lookupClass(thisClassName),
                constantPool.lookupClass(outerClassName),
                // If C is anonymous (JLS §15.9.5), the value of the
                // inner_name_index item must be zero.
                0,
                // They should be set to zero in generated class files and
                // should be ignored by Java Virtual Machine
                // implementations.
                0) }, constantPool.getConstantPool());
        cgen.addAttribute(inner);
    }

    private void doGenerateEnclosingMethodAttribute(final ClassGen cgen) throws ClassNotFoundException {
        final ConstantPoolGen constantPoolGen = cgen.getConstantPool();
        int outClassIndex = constantPoolGen.addClass(new ObjectType(this.outerClassName));
        final int enclosingMethodIndex = constantPoolGen.addUtf8("EnclosingMethod");
        final StringBuilder sb = new StringBuilder();
        sb.append("(");
        for ( Type t : enclosingMethodArguments ) {
            sb.append(t.getSignature());
        }
        sb.append(")");
        sb.append(lookupEnclosingMethodReturnType().getSignature());
        String enclosingMethodSignature = sb.toString();
        int enclosingMethodNameAndType = constantPoolGen.addNameAndType(this.enclosingMethodName, enclosingMethodSignature);
        // cgen.addAttribute(new
        // EnclosingMethodAttribute(enclosingMethodIndex, (short)
        // constantPoolGen
        // .lookupClass(this.outerClassName), (short)
        // enclosingMethodNameAndType, constantPoolGen
        // .getConstantPool()));
        // --------------------------------------------------------------------------------------
        // while parsing a Java class, parser will treat enclosingMethod as
        // unknown attribute
        byte[] bytes = new byte[] { ( (byte) ( ( outClassIndex & 0xFF00 ) >>> 8 ) ), (byte) ( outClassIndex & 0x00FF ),
                ( (byte) ( ( enclosingMethodNameAndType & 0xFF00 ) >>> 8 ) ), (byte) ( enclosingMethodNameAndType & 0x00FF ) };
        cgen.addAttribute(new Unknown(enclosingMethodIndex, 4, bytes, constantPoolGen.getConstantPool()));
    }

    private void doGenerateMethods(ClassGen cgen) throws ClassNotFoundException {
        createCall(cgen);
        final Type returnType = lookupEnclosingMethodReturnType();
        if ( !returnType.equals(new ObjectType(Object.class.getName())) ) {
            createBridgeCall(cgen);
        }
    }

    private void createBridgeCall(ClassGen cgen) throws ClassNotFoundException {
        InstructionFactory ifact = new InstructionFactory(cgen);
        InstructionList iList = new InstructionList();
        final LocalVariableInstruction start = InstructionConstants.ALOAD_0;
        iList.append(start);
        final Type returnType = lookupEnclosingMethodReturnType();
        final Type wrappedReturnType = convertWrappedReturnType(returnType);
        iList.append(ifact.createInvoke(thisClassName, "call", wrappedReturnType, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
        ReturnInstruction end = InstructionConstants.ARETURN;
        iList.append(end);
        final MethodGen callMethodGen = new MethodGen(4161, new ObjectType("java.lang.Object"), Type.NO_ARGS, new String[] {}, "call", thisClassName, iList,
                cgen.getConstantPool());
        callMethodGen.addException("java.lang.Exception");
        addMethod(cgen, callMethodGen);
        iList.dispose();
    }

    private void createCall(ClassGen cgen) throws ClassNotFoundException {
        final InstructionFactory ifact = new InstructionFactory(cgen);
        final InstructionList iList = new InstructionList();
        final List argumentVariables = lookupEnclosingMethodArgumentVariables();
        for ( LocalVariable var : argumentVariables ) {
            iList.append(InstructionConstants.ALOAD_0);
            if ( "this".equals(var.getName()) ) {
                iList.append(ifact.createGetField(thisClassName, "this$0", convertSignature2Type(var.getSignature())));
            } else {
                iList.append(ifact.createGetField(thisClassName, "val$" + var.getName(), convertSignature2Type(var.getSignature())));
            }
        }
        final Type returnType = lookupEnclosingMethodReturnType();
        final Type[] argTypes = lookupEnclosingMethodArgType();
        iList.append(ifact.createInvoke(outerClassName, enclosingMethodName + POSTFIX, returnType, argTypes, Constants.INVOKEVIRTUAL));
        final Instruction valueOfInstruction = createValueOf(ifact, returnType);
        if ( null != valueOfInstruction ) {
            iList.append(valueOfInstruction);
        }
        final Type wrappedReturnType = convertWrappedReturnType(returnType);
        iList.append(InstructionFactory.createReturn(Type.OBJECT));
        final MethodGen callMethodGen = new MethodGen(1, wrappedReturnType, Type.NO_ARGS, new String[] {}, "call", thisClassName, iList, cgen.getConstantPool());
        callMethodGen.addException("java.lang.Exception");
        addMethod(cgen, callMethodGen);
        iList.dispose();
    }

    private Instruction createValueOf(final InstructionFactory ifact, final Type returnType) {
        switch (returnType.getType()) {
            case Constants.T_VOID:
                return InstructionConstants.ACONST_NULL;
            case Constants.T_INT:
                return ifact.createInvoke(Integer.class.getName(), "valueOf", new ObjectType(Integer.class.getName()), new Type[] { Type.INT },
                        Constants.INVOKESTATIC);
            case Constants.T_LONG:
                return ifact.createInvoke(Long.class.getName(), "valueOf", new ObjectType(Long.class.getName()), new Type[] { Type.LONG },
                        Constants.INVOKESTATIC);
            case Constants.T_FLOAT:
                return ifact.createInvoke(Float.class.getName(), "valueOf", new ObjectType(Float.class.getName()), new Type[] { Type.FLOAT },
                        Constants.INVOKESTATIC);
            case Constants.T_DOUBLE:
                return ifact.createInvoke(Double.class.getName(), "valueOf", new ObjectType(Double.class.getName()), new Type[] { Type.DOUBLE },
                        Constants.INVOKESTATIC);
            case Constants.T_BYTE:
                return ifact.createInvoke(Byte.class.getName(), "valueOf", new ObjectType(Byte.class.getName()), new Type[] { Type.BYTE },
                        Constants.INVOKESTATIC);
            case Constants.T_SHORT:
                return ifact.createInvoke(Short.class.getName(), "valueOf", new ObjectType(Short.class.getName()), new Type[] { Type.SHORT },
                        Constants.INVOKESTATIC);
            case Constants.T_CHAR:
                return ifact.createInvoke(Character.class.getName(), "valueOf", new ObjectType(Character.class.getName()), new Type[] { Type.CHAR },
                        Constants.INVOKESTATIC);
            case Constants.T_BOOLEAN:
                return ifact.createInvoke(Boolean.class.getName(), "valueOf", new ObjectType(Boolean.class.getName()), new Type[] { Type.BOOLEAN },
                        Constants.INVOKESTATIC);
            default:
                return null;
        }
    }

    private Type convertWrappedReturnType(final Type returnType) {
        switch (returnType.getType()) {
            case Constants.T_VOID:
                return new ObjectType("java.lang.Void");
            case Constants.T_INT:
                return new ObjectType(Integer.class.getName());
            case Constants.T_LONG:
                return new ObjectType(Long.class.getName());
            case Constants.T_FLOAT:
                return new ObjectType(Float.class.getName());
            case Constants.T_DOUBLE:
                return new ObjectType(Double.class.getName());
            case Constants.T_BYTE:
                return new ObjectType(Byte.class.getName());
            case Constants.T_SHORT:
                return new ObjectType(Short.class.getName());
            case Constants.T_CHAR:
                return new ObjectType(Character.class.getName());
            case Constants.T_BOOLEAN:
                return new ObjectType(Boolean.class.getName());
            default:
                return returnType;
        }
    }

    private void doGenerateConstructor(ClassGen cgen) throws Throwable {
        InstructionFactory ifact = new InstructionFactory(cgen);
        InstructionList iList = new InstructionList();
        Field[] fields = cgen.getFields();
        int fieldIndex = 1;
        for ( int i = 0; i < fields.length; i++ ) {
            if ( "this$0".equals(fields[i].getName()) ) {
                iList.append(InstructionConstants.ALOAD_0);
                iList.append(InstructionConstants.ALOAD_1);
                iList.append(ifact.createPutField(this.thisClassName, "this$0", new ObjectType(this.outerClassName)));
                fieldIndex++;
            } else {
                iList.append(InstructionConstants.ALOAD_0);
                iList.append(InstructionFactory.createLoad(fields[i].getType(), fieldIndex));
                iList.append(ifact.createPutField(this.thisClassName, fields[i].getName(), fields[i].getType()));
                fieldIndex += fields[i].getType().getSize();
            }
        }
        iList.append(InstructionConstants.ALOAD_0);
        iList.append(ifact.createInvoke("java.lang.Object", "", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
        iList.append(InstructionFactory.createReturn(Type.VOID));
        final List arguments = lookupEnclosingMethodArgumentVariables();
        final ArrayList typeList = new ArrayList();
        final String[] argumentNames = new String[arguments.size()];
        for ( int i = 0; i < arguments.size(); i++ ) {
            if ( "this$0".equals(arguments.get(i).getName()) ) {
                int lastDot = this.outerClassName.lastIndexOf(".");
                String substring = this.outerClassName.substring(lastDot + 1);
                if ( substring.charAt(0) >= 'A' && substring.charAt(0) <= 'Z' ) {
                    substring = ( substring.charAt(0) + "" ).toLowerCase() + substring.substring(1);
                }
                typeList.add(convertSignature2Type(arguments.get(i).getSignature()));
                argumentNames[i] = substring;
            } else {
                typeList.add(convertSignature2Type(arguments.get(i).getSignature()));
                argumentNames[i] = arguments.get(i).getName();
            }
        }
        final MethodGen constructorGen = new MethodGen(0, net.imadz.org.apache.bcel.generic.Type.VOID, typeList.toArray(new Type[0]), argumentNames, "",
                this.thisClassName, iList, cgen.getConstantPool());
        addMethod(cgen, constructorGen);
        iList.dispose();
    }

    private void addMethod(ClassGen cgen, final MethodGen method) {
        method.setMaxLocals();
        method.setMaxStack();
        method.stripAttributes(true);
        cgen.addMethod(method.getMethod());
        cgen.setMinor(0);
        cgen.setMajor(50);
    }

    private void doGenerateSignatureAttribute(final ClassGen cgen) {
        final ConstantPoolGen constantPool = cgen.getConstantPool();
        StringBuilder sb = new StringBuilder();
        sb.append(createSignature(superClass, superClassTypeParameters));
        sb.append(createSignature(interfaceClass, interfaceClassTypeParameters));
        int signature_index = constantPool.addUtf8(sb.toString());
        int sig_name_index = constantPool.addUtf8("Signature");
        final Signature sig = new Signature(sig_name_index, 2, signature_index, constantPool.getConstantPool());
        cgen.addAttribute(sig);
    }

    private String createSignature(String klass, Type[] typeParameters) {
        StringBuilder sb = new StringBuilder();
        if ( 0 < typeParameters.length ) {
            sb.append("<");
            for ( int i = 0; i < typeParameters.length; i++ ) {
                sb.append(typeParameters[i].getSignature());
                if ( i != typeParameters.length - 1 ) {
                    sb.append(",");
                }
            }
            sb.append(">");
        }
        return new ObjectType(klass + sb.toString()).getSignature();
    }

    private void doGenerateFields(final ClassGen cgen) throws ClassNotFoundException {
        final List arguments = lookupEnclosingMethodArgumentVariables();
        for ( LocalVariable localVariable : arguments ) {
            doGenField(localVariable, cgen);
        }
    }

    private void doGenField(LocalVariable localVariable, ClassGen cgen) {
        if ( localVariable.getName().equals("this") ) {
            createField("this$0", localVariable.getSignature(), cgen);
        } else {
            createField("val$" + localVariable.getName(), localVariable.getSignature(), cgen);
        }
    }

    private void createField(String fieldName, String signature, ClassGen cgen) {
        Type type = null;
        if ( "this$0".equals(fieldName) ) {
            type = new ObjectType(this.outerClassName);
        } else {
            type = convertSignature2Type(signature);
        }
        final FieldGen serviceOrderField = new FieldGen(0x1010, type, fieldName, cgen.getConstantPool());
        cgen.addField(serviceOrderField.getField());
    }

    private Type convertSignature2Type(String signature) {
        switch (signature.charAt(0)) {
            case 'Z':
                return Type.BOOLEAN;
            case 'C':
                return Type.CHAR;
            case 'F':
                return Type.FLOAT;
            case 'D':
                return Type.DOUBLE;
            case 'B':
                return Type.BYTE;
            case 'S':
                return Type.SHORT;
            case 'I':
                return Type.INT;
            case 'J':
                return Type.LONG;
            default:
                /*
                 * //process type with TypeParameters, such as
                 * Ljava.util.concurrent.Call;
                 * if ( signature.startsWith("L") ) {
                 * signature = ";" + signature;
                 * }
                 * signature = signature.replace(";L", "");
                 * signature = signature.replace(";", "");
                 */
                if ( signature.startsWith("L") ) {
                    int firstLeftArrow = signature.indexOf("<");
                    if ( -1 == firstLeftArrow ) {
                        signature = signature.substring(1);
                    } else {
                        signature = signature.substring(1, firstLeftArrow);
                    }
                    if ( signature.endsWith(";") ) {
                        signature = signature.substring(0, signature.length() - 1);
                    }
                }
                return new ObjectType(signature);
        }
    }

    private List lookupEnclosingMethodArgumentVariables() throws ClassNotFoundException {
        final JavaClass outerClass = lookupOuterClass();
        for ( final Method method : outerClass.getMethods() ) {
            if ( isNotEnclosingMethod(method) ) {
                continue;
            }
            final LocalVariableTable localVariableTable = method.getLocalVariableTable();
            if ( null != localVariableTable ) {
                return populateNamedArgumentsFromLocalVariableTable(localVariableTable);
            } else {
                return populateNonamedMethodArguments(method);
            }
        }
        return new ArrayList();
    }

    private boolean isNotEnclosingMethod(final Method method) {
        return !enclosingMethodName.equals(method.getName()) || !argumentsMatch(method.getArgumentTypes());
    }

    private List populateNonamedMethodArguments(Method method) {
        int i = 1;
        final List result = new ArrayList();
        result.add(new LocalVariable("this", new ObjectType(outerClassName).getSignature()));
        for ( Type argumentType : method.getArgumentTypes() ) {
            result.add(new LocalVariable("" + i++, argumentType.getSignature()));
        }
        return result;
    }

    private List populateNamedArgumentsFromLocalVariableTable(final LocalVariableTable localVariableTable) {
        final List result = new ArrayList();
        for ( net.imadz.org.apache.bcel.classfile.LocalVariable localVariable : localVariableTable.getLocalVariableTable() ) {
            if ( localVariable.getStartPC() == 0 ) {
                result.add(new LocalVariable(localVariable.getName(), localVariable.getSignature()));
            }
        }
        return result;
    }

    private Type lookupEnclosingMethodReturnType() throws ClassNotFoundException {
        final JavaClass outerClass = lookupOuterClass();
        for ( Method method : outerClass.getMethods() ) {
            if ( !enclosingMethodName.equals(method.getName()) ) {
                continue;
            }
            if ( !argumentsMatch(method.getArgumentTypes()) ) {
                continue;
            }
            return method.getReturnType();
        }
        throw new IllegalStateException("Cannot find enclosingMethod: " + enclosingMethodName);
    }

    private JavaClass lookupOuterClass() throws ClassNotFoundException {
        return SyntheticRepository.getInstance(new ClassPath(location)).loadClass(this.outerClassName);
        // lookupClass(classLoader.loadClass(this.outerClassName.replaceAll("\\/",
        // ".")));
    }

    private Type[] lookupEnclosingMethodArgType() throws ClassNotFoundException {
        final JavaClass outerClass = lookupOuterClass();
        for ( Method method : outerClass.getMethods() ) {
            if ( !enclosingMethodName.equals(method.getName()) ) {
                continue;
            }
            if ( !argumentsMatch(method.getArgumentTypes()) ) {
                continue;
            }
            return method.getArgumentTypes();
        }
        throw new IllegalStateException("Cannot find enclosingMethod: " + enclosingMethodName);
    }

    private boolean argumentsMatch(Type[] argumentTypes) {
        if ( enclosingMethodArguments.length != argumentTypes.length ) {
            return false;
        } else {
            for ( int i = 0; i < argumentTypes.length; i++ ) {
                if ( !enclosingMethodArguments[i].equals(argumentTypes[i]) ) return false;
            }
        }
        return true;
    }

    public static class LocalVariable {

        private final String name;
        private final String signature;

        public LocalVariable(String name, String signature) {
            this.name = name;
            this.signature = signature;
        }

        String getName() {
            return name;
        }

        String getSignature() {
            return signature;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy