/*
 * @(#)ModifiedMethod.java
 *
 * Copyright (C) 2002-2004 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.codecoverage.v2.compiler;

import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;

/**
 * Refers to a class file that has been modified with additional logging
 * statements.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/04/15 05:48:25 $
 * @since     December 17, 2002
 */
public class ModifiedMethod
{
    private static final org.apache.log4j.Logger LOG =
        org.apache.log4j.Logger.getLogger( ModifiedMethod.class );
    private JavaClass origClass;
    private Method origMethod;
    private MethodGen modMethGen;
    private Method modMeth;
    private boolean closed = false;
    private ModifiedInstructionList modInstructions;
    
    /**
     * constant pool index for the name of the class's signature.
     */
    private int classSigPoolIndex;
    
    /**
     * constant pool index for the method-ref for invoking the logger.
     */
    private int staticMethodPoolIndex;
    
    /**
     * the original class file's checksum.
     */
    private long checksum;
    
    /**
     * Reference to this method's index
     */
    private final short methodIndex;
    
    /**
     * @throws IllegalStateException if the class file has already been
     *         modified (identified by a class name field).
     */
    ModifiedMethod( short methodIndex, int classSigPoolIndex,
            int staticMethodPoolIndex, JavaClass origC, Method origM,
            MethodGen mg )
    {
        if (mg == null || origC == null || origM == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        this.methodIndex = methodIndex;
        this.classSigPoolIndex = classSigPoolIndex;
        this.staticMethodPoolIndex = staticMethodPoolIndex;
        
        this.modMethGen = mg;
        this.origClass = origC;
        this.origMethod = origM;
    }
    
    
    public short getMethodIndex()
    {
        return this.methodIndex;
    }
    
    
    public String getMethodName()
    {
        return this.origMethod.getName() + this.origMethod.getSignature();
    }
    
    
    /**
     * If the method cannot be modified, then this will return <tt>null</tt>.
     */
    public ModifiedInstructionList getInstructionList()
    {
        checkClose();
        if (this.modInstructions == null && canAddMarks())
        {
            createInstructionList();
            
            // the above call can create a null list if the generated list
            // is bad.
        }
        return this.modInstructions;
    }
    
    
    public JavaClass getOriginalClass()
    {
        return this.origClass;
    }
    
    
    public Method getOriginalMethod()
    {
        return this.origMethod;
    }
    
    
    /**
     * Returns <tt>true</tt> if the method can be modified, otherwise returns
     * <tt>false</tt>.  A method can be modified only if it is not native,
     * not abstract, and it has a code attribute.
     */
    public boolean canAddMarks()
    {
        return ModifiedClass.isMarkable( this.origMethod );
    }
    
    
    //------------------------------------------------------------------------
    
    
    /**
     * The modified method generator is only valid <i>before</i> a close.
     */
    MethodGen getModifiedMethodGen()
    {
        checkClose();
        return this.modMethGen;
    }
    
    
    /**
     * This should only be called *after* a close.
     */
    Method getNewMethod()
    {
        if (!this.closed || this.modMeth == null)
        {
            throw new IllegalStateException(
                "ModifiedMethod has not been closed." );
        }
        return this.modMeth;
    }
    
    
    void close()
    {
        checkClose();
        
        this.closed = true;
        if (this.modInstructions != null)
        {
            LOG.debug( "Setting the modified instruction list." );
            this.modInstructions.updateInstructionList();
            this.modMethGen.setMaxLocals();
            this.modMethGen.setMaxStack();
            this.modMeth = this.modMethGen.getMethod();
            this.modInstructions.close();
            this.modInstructions = null;
            
            /*
            InstructionList finalList = this.modMethGen.getInstructionList();
            LOG.debug( "Final modified method: ["+this.modMethGen+
                "], instructions (size="+finalList.getLength()+") ["+finalList+
                "]." );
            */
            this.modMethGen = null;
        }
        else
        if (this.modMeth == null)
        {
            this.modMeth = this.modMethGen.getMethod();
            
            /*
            InstructionList finalList = this.modMethGen.getInstructionList();
            LOG.debug( "Final modified method: ["+this.modMethGen+
                "], instructions (size="+finalList.getLength()+") ["+finalList+
                "]." );
            */
            this.modMethGen = null;
        }
    }
    
    
    //------------------------------------------------------------------------
    
    
    /*
     * The class cannot be closed when this is called, or else an NPE will
     * be thrown.
     */
    private void createInstructionList()
    {
        InstructionList list = getModifiedMethodGen().getInstructionList();
        if (ModifiedInstructionList.isValidInstructionList( list ))
        {
            this.modInstructions = new ModifiedInstructionList(
                this.methodIndex, this.classSigPoolIndex,
                this.staticMethodPoolIndex, list,
                new ModifiedTargeters( this.modMethGen ) );
        }
        else
        {
            LOG.warn( "Instruction list for method ["+getMethodName()+
                "] in class ["+getOriginalClass().getClassName()+
                "] is invalid." );
        }
        // else the list is invalid.
    }
    
    
    private void checkClose()
    {
        if (this.closed)
        {
            throw new IllegalStateException("Method has been closed.");
        }
    }
}

