Package org.apache.avalon.fortress.impl.factory

Source Code of org.apache.avalon.fortress.impl.factory.BCELCodeGenerator

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.avalon.fortress.impl.factory;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.*;

import java.util.HashSet;
import java.util.Set;

/**
* <p>
<code>BCELCodeGenerator</code> creates implementations for the
{@link org.apache.bcel.classfile.Method Method}s and
{@link org.apache.bcel.classfile.Field Field}s needed in creating a
<code>WrapperClass</code>.
* </p>
*
* @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
*/

public final class BCELCodeGenerator
{
    //***************************************************************************
    // Fields
    //***************************************************************************

    /**
     * The name of the field holding the wrapped class in the generated
     * wrapper, e.g.
     * <pre>
     <code>
     *      private final <ClassToWrap> WRAPPED_CLASS_FN;
     *  </code>
     * </pre>
     */
    private static final String WRAPPED_CLASS_FN = "m_wrappedClass";

    /**
     * The name of the field accessor used to ask the generated wrapper for
     * the wrapped class instance.
     */
    private static final String ACCESSOR_METHOD_NAME = "getWrappedObject";

    /**
     * The name of the wrapper class to be created.
     */
    private String m_wrapperClassName;

    /**
     * The name of the superclass of the wrapper class to be generated.
     */
    private String m_wrapperSuperclassName;

    /**
     * Class object holding the type of the object we want to create a
     * wrapper for.
     */
    private JavaClass m_classToWrap;

    /**
     * The {@link org.apache.bcel.generic.Type Type} of the class we want to
     * create a wrapper for.
     */
    private Type m_classToWrapType;

    /**
     * The {@link org.apache.bcel.generic.ClassGen ClassGen} instance to use for
     * code generation.
     */
    private ClassGen m_classGenerator;

    /**
     * The {@link org.apache.bcel.generic.ConstantPoolGen ConstantPoolGen}
     * instance to use for code generation.
     */
    private ConstantPoolGen m_constPoolGenerator;

    /**
     * The {@link org.apache.bcel.generic.InstructionList InstructionList} instance
     * to use during code generation.
     */
    private final InstructionList m_instructionList = new InstructionList();

    /**
     * The {@link org.apache.bcel.generic.InstructionFactory InstructionFactory} to
     * use during code gereration.
     */
    private InstructionFactory m_instructionFactory;

    /**
     * Flag indicating whether this instance is already initialized or not.
     */
    private boolean m_isInitialized = false;

    /**
     * Default constructor.
     */
    public BCELCodeGenerator()
    {
        // Left blank
    }

    public void init(
        final String wrapperClassName,
        final String wrapperSuperclassName,
        final JavaClass classToWrap,
        final ClassGen classGenerator )
        throws IllegalArgumentException
    {
        if ( classToWrap == null )
        {
            final String message = "Target class must not be <null>.";
            throw new IllegalArgumentException( message );
        }
        if ( classToWrap.isAbstract() || !classToWrap.isClass() )
        {
            final String message =
                "Target class must neither be abstract nor an interface.";
            throw new IllegalArgumentException( message );
        }
        if ( classGenerator == null )
        {
            final String message = "ClassGenerator must not be <null>.";
            throw new IllegalArgumentException( message );
        }

        m_wrapperClassName = wrapperClassName;
        m_wrapperSuperclassName = wrapperSuperclassName;
        m_classToWrap = classToWrap;
        m_classToWrapType = new ObjectType( m_classToWrap.getClassName() );
        m_classGenerator = classGenerator;
        m_constPoolGenerator = m_classGenerator.getConstantPool();
        m_instructionFactory =
            new InstructionFactory( m_classGenerator, m_constPoolGenerator );

        m_isInitialized = true;
    }

    /**
     * Create a field declaration of the form
     * <pre>
     <code>
     *      private <ClassToWrap> WRAPPED_CLASS_FN;
     *  </code>
     * </pre>
     *
     * @return Field
     *
     * @throws IllegalStateException If this instance is not initialized.
     */
    public Field createWrappedClassField() throws IllegalStateException
    {
        if ( !isInitialized() )
        {
            final String message =
                "BCELMethodFieldImplementationGenerator is not initialized.";
            throw new IllegalStateException( message );
        }

        final FieldGen fg =
            new FieldGen(
                Constants.ACC_PRIVATE,
                m_classToWrapType,
                WRAPPED_CLASS_FN,
                m_constPoolGenerator );

        return fg.getField();
    }

    /**
     * Create the wrapper class' default constructor:
     * <pre>
     <code>
     *      public <wrapperClass>(<classToWrap> classToWrap)
     *      {
     *          this.<WRAPPED_CLASS_FN> = classToWrap;
     *      }
     *  </code>
     * </pre>
     *
     * @return The created default constructor
     *
     * @throws IllegalStateException If this instance is not initialized.
     */
    public Method createDefaultConstructor() throws IllegalStateException
    {
        if ( !isInitialized() )
        {
            final String message =
                "BCELMethodFieldImplementationGenerator is not initialized.";
            throw new IllegalStateException( message );
        }
        final MethodGen mg =
            new MethodGen(
                Constants.ACC_PUBLIC,
                Type.VOID,
                new Type[]{m_classToWrapType},
                new String[]{"classToWrap"},
                "<init>",
                m_wrapperClassName,
                m_instructionList,
                m_constPoolGenerator );

        m_instructionList.append(
                InstructionFactory.createLoad( Type.OBJECT, 0 ) );
        m_instructionList.append(
            m_instructionFactory.createInvoke(
                m_wrapperSuperclassName,
                "<init>",
                Type.VOID,
                Type.NO_ARGS,
                Constants.INVOKESPECIAL ) );
        m_instructionList.append(
                InstructionFactory.createLoad( Type.OBJECT, 0 ) );
        m_instructionList.append(
                InstructionFactory.createLoad( Type.OBJECT, 1 ) );
        m_instructionList.append(
            m_instructionFactory.createFieldAccess(
                m_wrapperClassName,
                WRAPPED_CLASS_FN,
                m_classToWrapType,
                Constants.PUTFIELD ) );
        m_instructionList.append( InstructionFactory.createReturn( Type.VOID ) );
        mg.setMaxStack();
        mg.setMaxLocals();

        return extractMethod( mg );
    }

    /**
     * Create a field accessor for the wrapped class instance of the form
     * <pre>
     <code>
     *      public Object <ACCESSOR_METHOD_NAME>()
     *      {
     *          return this.<WRAPPED_CLASS_FN>;
     *      }
     *  </code>
     * </pre>
     * @return Method
     * @throws IllegalStateException
     */
    public Method createWrappedClassAccessor() throws IllegalStateException
    {
        if ( !isInitialized() )
        {
            final String message =
                "BCELMethodFieldImplementationGenerator is not initialized.";
            throw new IllegalStateException( message );
        }

        final MethodGen mg =
            new MethodGen(
                Constants.ACC_PUBLIC,
                Type.OBJECT,
                Type.NO_ARGS,
                new String[]{
                },
                ACCESSOR_METHOD_NAME,
                m_classToWrap.getClassName(),
                m_instructionList,
                m_constPoolGenerator );

        m_instructionList.append(
                InstructionFactory.createLoad( Type.OBJECT, 0 ) );
        m_instructionList.append(
            m_instructionFactory.createFieldAccess(
                m_wrapperClassName,
                WRAPPED_CLASS_FN,
                m_classToWrapType,
                Constants.GETFIELD ) );
        m_instructionList.append(
                InstructionFactory.createReturn( Type.OBJECT ) );

        mg.setMaxStack();
        mg.setMaxLocals();

        return extractMethod( mg );
    }

    /**
     * Create a method declaration/definition of the form
     * <pre>
     <code>
     *      public <returnType> <methodName>(<parameterTypes>)
     *          throws <exceptionNames>
     *      {
     *          return this.<WRAPPED_CLASS_FN>.<methodName>(<parameterTypes>);
     *      }
     *  </code>
     * </pre>
     *
     * @param meth           The method descriptor
     *
     * @return Method         The {@link org.apache.bcel.classfile.Method Method}
     *                        object representing the created method
     *
     * @throws IllegalArgumentException If any of the parameters passed in is null.
     * @throws IllegalStateException If this instance is not initialized.
     */
    public Method createMethodWrapper( MethodDesc meth )
        throws IllegalArgumentException, IllegalStateException
    {
        if ( !isInitialized() )
        {
            final String message =
                "BCELMethodFieldImplementationGenerator is not initialized.";
            throw new IllegalStateException( message );
        }
        if ( meth.name == null
            || meth.returnType == null
            || meth.parameterTypes == null
            || meth.exceptionNames == null )
        {
            final String message = "None of the parameters may be <null>.";
            throw new IllegalArgumentException( message );
        }

        final MethodGen mg =
            new MethodGen(
                Constants.ACC_PUBLIC,
                meth.returnType,
                meth.parameterTypes,
                null,
                meth.name,
                m_wrapperClassName,
                m_instructionList,
                m_constPoolGenerator );

        // Create throws clause
        for ( int i = 0; i < meth.exceptionNames.length; i++ )
        {
            mg.addException( meth.exceptionNames[i] );
        }

        // Loading the wrapped class instance onto the stack ...
        m_instructionList.append(
                InstructionFactory.createLoad( Type.OBJECT, 0 ) );
        m_instructionList.append(
            m_instructionFactory.createFieldAccess(
                m_wrapperClassName,
                WRAPPED_CLASS_FN,
                m_classToWrapType,
                Constants.GETFIELD ) );

        // Loading all parameters onto the stack ...
        short stackIndex = 1;
        // Stack index 0 is occupied by the wrapped class instance.
        for ( int i = 0; i < meth.parameterTypes.length; ++i )
        {
            m_instructionList.append(
                    InstructionFactory.createLoad( meth.parameterTypes[i], stackIndex ) );
            stackIndex += meth.parameterTypes[i].getSize();
        }

        findImplementation( meth );

        // Invoking the specified method with the loaded parameters on
        // the wrapped class instance ...
        m_instructionList.append(
            m_instructionFactory.createInvoke(
                    meth.implementingClassName,
                    meth.name,
                    meth.returnType,
                    meth.parameterTypes,
                    Constants.INVOKEVIRTUAL ) );

        // Creating return statement ...
        m_instructionList.append( InstructionFactory.createReturn( meth.returnType ) );

        mg.setMaxStack();
        mg.setMaxLocals();

        return extractMethod( mg );
    }

    private void findImplementation( MethodDesc meth )
    {
        JavaClass currentClass = m_classToWrap;

        while ( null != currentClass && null == meth.implementingClassName )
        {
            Method[] methList = currentClass.getMethods();

            for (int i = 0; i < methList.length; i++)
            {
                boolean isEqual = methList[i].isPublic() && !methList[i].isAbstract();
                isEqual = isEqual && methList[i].getName().equals(meth.name);
                isEqual = isEqual && methList[i].getReturnType().equals(meth.returnType);
                isEqual = isEqual && methList[i].getArgumentTypes().length == meth.parameterTypes.length;
                Type[] parameterTypes = methList[i].getArgumentTypes();
                for (int j = 0; isEqual && j < parameterTypes.length; j++)
                {
                    isEqual = isEqual && parameterTypes[j].equals(meth.parameterTypes[j]);
                }

                if (isEqual)
                {
                    meth.implementingClassName = currentClass.getClassName();
                    meth.isFinal = methList[i].isFinal();
                }
            }
            try
            {
                currentClass = currentClass.getSuperClass();
            } catch( Exception e )
            {
                // NH: Actually ClassNotFoundException is declared in the current CVS
                //     Head, but then we can't compile against BCEL5.5 which is our
                //     binary dependency, so I am catching Exception to cover both.
                //
                //     I think this will happen if the superclass is not available
                //     in the classloaders, but I have no clue what is the proper action.
                //     I had to introduce this since BCEL decided to break compatibility
                //     and now throws the Exception.
                //     For now, throwing the absurd IllegalStateException as the rest
                //     of the codebase seems to be constructed around it.
                throw new IllegalStateException("Superclass of java class could not be found: " + currentClass + ", " + e.getMessage() );
            }
        }

        if (null == meth.implementingClassName)
        {
            throw new IllegalStateException("No concrete class for the requested method: " + meth.toString());
        }
    }

    /**
     * Create a method declaration/definition of the form
     * <pre>
     <code>
     *      public <returnType> <methodName>(<parameterTypes>)
     *          throws <exceptionNames>
     *      {
     *          return this.<WRAPPED_CLASS_FN>.<methodName>(<parameterTypes>);
     *      }
     *  </code>
     * </pre>
     *
     * @param methodToWrap The <code>Method</code> to create a wrapper for.
     *
     * @return Method       The wrapper method.
     *
     * @throws IllegalArgumentException If <code>methodToWrao</code> is null.
     * @throws IllegalStateException If this instance is not initialized.
     */
    public Method createMethodWrapper( final Method methodToWrap )
        throws IllegalArgumentException, IllegalStateException
    {
        if ( methodToWrap == null )
        {
            final String message = "Method parameter must not be <null>.";
            throw new IllegalArgumentException( message );
        }

        return createMethodWrapper( new MethodDesc( methodToWrap ) );
    }

    /**
     * Creates an implementation for the supplied {@link org.apache.bcel.classfile.JavaClass JavaClass}
     * instance representing an interface.
     *
     * @param interfacesToImplement The interfaces we want to create an implementation for
     * @return Method[]            An array of {@link org.apache.bcel.classfile.Method Method}
     *                              instances representing the interface implementation.
     * @throws IllegalArgumentException If <code>interfaceToImplement</code> is <code>null</code>
     *                                   or does not represent an interface
     * @throws IllegalStateException    If this instance has not been initialized
     */
    public Method[] createImplementation( final JavaClass[] interfacesToImplement )
        throws Exception
    {
        if ( interfacesToImplement == null )
        {
            final String message = "Interface to implement must not be <null>.";
            throw new IllegalArgumentException( message );
        }
        if ( !isInitialized() )
        {
            final String message =
                "BCELInterfaceImplementationGenerator is not initialized.";
            throw new IllegalStateException( message );
        }
        final Set gmList = new HashSet();

        final MethodDesc[] interfaceMethods = extractMethods( interfacesToImplement );
        for ( int i = 0; i < interfaceMethods.length; ++i )
        {
            final MethodDesc im = interfaceMethods[i];

            // Skip <clinit> method ...
            if ( im.name.equals( "<clinit>" ) )
            {
                continue;
            }

            final Method generatedMethod =
                createMethodWrapper( im );

            gmList.add( generatedMethod );
        }

        return (Method[]) gmList.toArray( new Method[gmList.size()] );
    }

    /**
     * Extracts the {@link org.apache.bcel.classfile.Method Method} out of
     * the supplied {@link org.apache.bcel.generic.MethodGen MethodGen} instance,
     * clears the {@link org.apache.bcel.generic.InstructionList InstructionList}
     * and returns the extracted <code>Method</code>.
     *
     * @param mg The {@link org.apache.bcel.generic.MethodGen MethodGen} instance
     *            holding the {@link org.apache.bcel.classfile.Method Method} to
     *            extract
     * @return   The extracted {@link org.apache.bcel.classfile.Method Method}
     */
    private Method extractMethod( final MethodGen mg )
    {
        final Method m = mg.getMethod();
        m_instructionList.dispose();
        return m;
    }

    /**
     * Has this instance already been initialized?
     *
     * @return TRUE, if this instance has already been initialized, FALSE otherwise
     */
    private boolean isInitialized()
    {
        return m_isInitialized;
    }

    /**
     * Extracts the collection of {@link org.apache.bcel.classfile.Method Method}s
     * declared in the supplied {@link org.apache.bcel.classfile.JavaClass JavaClass}
     * instance. This instance is supposed to represent an interface.
     *
     * @param interfacesToImplement The {@link org.apache.bcel.classfile.JavaClass JavaClass}
     *                              instances representing the interfaces we are asking for
     *                              its methods.
     * @return MethodDesc[]         The array of {@link org.apache.bcel.classfile.Method Method}s
     *                              declared by the interface
     * @throws IllegalArgumentException If <code>interfaceToImplement</code> does not represent an interface
     * @throws NullPointerException if the <code>interfaceToImplement</code> is <code>null</code>
     */
    static MethodDesc[] extractMethods( final JavaClass interfacesToImplement[] )
        throws Exception
    {
        if ( interfacesToImplement == null )
        {
            final String message = "JavaClass[] parameter must not be <null>.";
            throw new NullPointerException( message );
        }

        Set methods = new HashSet();
        for (int x = 0; x < interfacesToImplement.length; x++)
        {
            JavaClass iface = interfacesToImplement[x];
            if ( !iface.isInterface() )
            {
                final String message = "JavaClass parameter must be an interface";
                throw new IllegalArgumentException( message );
            }

            extractMethods( iface, methods );
        }

        return (MethodDesc[]) methods.toArray( new MethodDesc[]{} );
    }

    private static final void extractMethods( final JavaClass interfaceToImplement, final Set methods )
    {
        Method[] meth = interfaceToImplement.getMethods();
        for ( int m = 0; m < meth.length; m++ )
        {
            MethodDesc desc = new MethodDesc(meth[m]);
            methods.add( desc );
        }
    }

    private static final class MethodDesc
    {
        final String name;
        final Type returnType;
        final Type[] parameterTypes;
        String[] exceptionNames;
        boolean isFinal;
        String implementingClassName;

        MethodDesc( Method meth )
        {
            this(meth.getName(), meth.getReturnType(), meth.getArgumentTypes(),
                    (null == meth.getExceptionTable() ) ? new String[0] : meth.getExceptionTable().getExceptionNames());
        }

        MethodDesc(String name, Type returnType, Type[] parameterTypes, String[] exceptionNames)
        {
            this.name = name;
            this.returnType = returnType;
            this.parameterTypes = parameterTypes;
            this.exceptionNames = exceptionNames;
            isFinal = false;
        }

        public boolean equals(Object o)
        {
            MethodDesc other = (MethodDesc)o;
            boolean isEqual = name.equals(other.name);
            isEqual = isEqual && returnType.equals(other.returnType);
            isEqual = isEqual && parameterTypes.length == other.parameterTypes.length;

            for (int i = 0; isEqual && i < parameterTypes.length; i++)
            {
                isEqual = isEqual && parameterTypes[i].equals(other.parameterTypes[i]);
            }

            return isEqual;
        }

        public int hashCode()
        {
            int hash = name.hashCode();
            hash >>>= 5;
            hash ^= returnType.hashCode();

            for (int i = 0; i < parameterTypes.length; i++)
            {
                hash >>>= parameterTypes.length;
                hash ^= parameterTypes[i].hashCode();
            }

            return hash;
        }

        public String toString()
        {
            StringBuffer str = new StringBuffer(returnType.getSignature());
            str.append( " " ).append( name ).append( "(" );

            for(int i = 0; i < parameterTypes.length; i++)
            {
                str.append(parameterTypes[i].toString());
                if (i > 0) str.append(",");
            }

            str.append("),").append((isFinal) ? "f" : "v");

            return str.toString();
        }
    }
}
TOP

Related Classes of org.apache.avalon.fortress.impl.factory.BCELCodeGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.