/*
 * @(#)ModifiedInstructionList.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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;

/**
 * Refers to a class file that has been modified with additional logging
 * statements.
 * <P>
 * As of January 22, 2003, the appending of probes to the end of the
 * instruction list is no longer supported; supporting it would mean that
 * instructions will be added to a method after the final "return" calls,
 * which might cause class file validation errors.
 *
 * @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 ModifiedInstructionList
{
    private static final org.apache.log4j.Logger LOG =
        org.apache.log4j.Logger.getLogger( ModifiedInstructionList.class );
    private boolean closed = false;
    private InstructionList modList; // this is the actual original list
    private List marked;
    
    /**
     * 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;
    
    /**
     * Reference to the owning method's index
     */
    private short methodIndex;
    
    /**
     * Keep track of the original instructions vs. their replacement for
     * target heads and tails.
     */
    private ModifiedTargeters targeters;
    
    /**
     * @throws IllegalStateException if the class file has already been
     *         modified (identified by a class name field).
     */
    ModifiedInstructionList( short methodIndex, int classSigPoolIndex,
            int staticMethodPoolIndex, InstructionList list,
            ModifiedTargeters mt )
    {
        if (list == null || mt == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        this.methodIndex = methodIndex;
        this.classSigPoolIndex = classSigPoolIndex;
        this.staticMethodPoolIndex = staticMethodPoolIndex;
        
        if (!isValidInstructionList( list ))
        {
            throw new IllegalArgumentException( "Instruction list contains "+
                "unsupported instruction setup." );
        }
        this.modList = list;
        this.targeters = mt;
        
        // there is a bug in the list.copy() that causes some
        // LOOKUPSWITCH instructions to become corrupted in the original!
        setupMarkList();
    }
    
    
    //------------------------------------------------------------------------
    
    
    public static boolean isValidInstructionList( InstructionList list )
    {
        try
        {
            list.getByteCode();
        }
        catch (Exception ex)
        {
            return false;
        }
        return true;
    }
    
    
    
    /**
     * This method can safely be called multiple times.
     *
     * Bug: We need to keep track of the original handle, and the corresponding
     * new handle that goes before it, so that we can go back and recreate the
     * CodeException objects for the method.
     */
    void updateInstructionList()
    {
        checkClose();
        
        LOG.debug( "********************************" );
        Iterator iter = this.marked.iterator();
        while (iter.hasNext())
        {
            MarkedInstruction mi = (MarkedInstruction)iter.next();
            
            InstructionList list = mi.getMarkedList();
            if (list != null && list.getLength() > 0)
            {
                InstructionHandle instr = mi.getHandle();
                LOG.debug( "Adding list (length = "+list.getLength()+
                    ", value='"+list+"') before handle '"+instr+"'." );
                InstructionHandle probe;
                if (mi.getInstruction() instanceof NullInstruction)
                {
                    // the null instruction will not add itself, but will
                    // append its marks to the end of the method.
                    /*
                    LOG.debug( "Appending probe to end of instructions." );
                    probe = this.modList.append( list );
                    this.targeters.appendProbe( probe );
                    */
                    LOG.warn(
                        "Appending probes to end of instructions is no longer allowed" );
                }
                else
                {
                    // normal instructions will insert their marks before
                    // the instruction is executed.
                    LOG.debug( "Inserting probe before ["+instr+"]" );
                    probe = this.modList.insert( instr, list );
                    this.targeters.insertProbe( instr, probe );
                }
            }
        }
        this.targeters.update();
        LOG.debug( "Final modified list = '"+this.modList+"'" );
    }
    
    
    public int getInstructionCount()
    {
        checkClose();
        
        // ignore the final NullInstruction
        return this.marked.size() - 1;
    }
    
    
    public MarkedInstruction getInstructionAt( int index )
    {
        checkClose();
        
        return (MarkedInstruction)this.marked.get( index );
    }
    
    
    void close()
    {
        checkClose();
        
        this.closed = true;
        this.modList.dispose();
        this.marked = null;
        this.modList = null;
    }
    
    
    //------------------------------------------------------------------------
    
    
    private void setupMarkList()
    {
        this.marked = new ArrayList();
        //Iterator iter = this.instructions.iterator();
        Iterator iter = this.modList.iterator();
        while (iter.hasNext())
        {
            InstructionHandle ih = (InstructionHandle)iter.next();
            this.marked.add( new MarkedInstruction( this.methodIndex,
                this.classSigPoolIndex, this.staticMethodPoolIndex, ih ) );
        }
        
        // add in the final instruction to allow for marking the end
        // of the method.
        InstructionList il = new InstructionList();
        InstructionHandle ih = il.append( new NullInstruction() );
        this.marked.add( new MarkedInstruction( this.methodIndex,
            this.classSigPoolIndex, this.staticMethodPoolIndex, ih ) );
    }
    
    
    private void checkClose()
    {
        if (this.closed)
        {
            throw new IllegalStateException("Method has been closed.");
        }
    }
}

