/*
 * @(#)DefaultMethodCode.java
 *
 * Copyright (C) 2002,2003 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 net.sourceforge.groboutils.codecoverage.v2.IAnalysisMetaData;
import net.sourceforge.groboutils.codecoverage.v2.IAnalysisModule;
import net.sourceforge.groboutils.codecoverage.v2.IMethodCode;
import net.sourceforge.groboutils.codecoverage.v2.datastore.ClassRecord;
import net.sourceforge.groboutils.codecoverage.v2.datastore.MarkRecord;

import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Instruction;


/**
 * Implements the per-module interface to the method's code and marking
 * the code.  Controls the module's index and the current module's mark
 * index for this method.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @author    Stefano Turri (stefano.turri AT it.ibm.com)
 * @version   $Date: 2004/04/20 23:12:06 $
 * @since     December 17, 2002
 */
public class DefaultMethodCode implements IMethodCode
{
    private static final org.apache.log4j.Logger LOG =
        org.apache.log4j.Logger.getLogger( DefaultMethodCode.class );
    
    private final ModifiedMethod method;
    private final ModifiedInstructionList list;
    private final String className;
    private final String methodName;
    private final IAnalysisModule analysisModule;
    private final short measureIndex;
    private final ClassRecord cr;
    private short markCount = 0;
    
    
    DefaultMethodCode( short measureIndex, ModifiedMethod mm, ClassRecord cr )
    {
        if (mm == null || cr == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        
        this.method = mm;
        this.list = mm.getInstructionList();
        if (this.list == null)
        {
            throw new IllegalStateException("abstract method "+mm);
        }
        this.className = mm.getOriginalClass().getClassName();
        this.methodName = mm.getMethodName();
        this.measureIndex = measureIndex;
        this.cr = cr;
        this.analysisModule = this.cr.getAnalysisModuleSet().
            getAnalysisModuleAt( measureIndex );
        
        // ensure the class record is kosher
        if (!this.className.equals( cr.getClassName() ))
        {
            throw new IllegalArgumentException(
                "modified method class ("+this.className+
                ") and class record class name ("+cr.getClassName()+
                ") do not match: " );
        }
        if (cr.getMethodIndex( this.methodName ) < 0)
        {
            throw new IllegalArgumentException(
                "method name ("+this.methodName+
                ") and class record do not match" );
        }
    }
    
    
    /**
     * Returns the original BCEL Method object.
     *
     * @return the original BCEL Method object
     */
    public Method getOriginalMethod()
    {
        return this.method.getOriginalMethod();
    }
    
    
    /**
     * Returns the line number table for the original BCEL method object.
     *
     * @return the LineNumberTable, or <tt>null</tt> if there isn't one
     *      for this method.
     */
    public LineNumberTable getLineNumberTable()
    {
        return getOriginalMethod().getLineNumberTable();
    }
    
    
    /**
     * A helper to get the method name.
     *
     * @return the method name
     */
    public String getMethodName()
    {
        return this.methodName;
    }
    
    
    /**
     * A helper to get the class name.
     *
     * @return the class name
     */
    public String getClassName()
    {
        return this.className;
    }
    
    
    /**
     * Returns the number of bytecode instructions in the method.
     *
     * @return the number of bytecode instructions
     */
    public int getInstructionCount()
    {
        return this.list.getInstructionCount();
    }
    
    
    /**
     * Returns the bytecode instruction at the given index.  If the index
     * is out of range (&lt; 0 or &gt;= <tt>getInstructionCount()</tt>),
     * then a <tt>IndexOutOfBoundsException</tt> is thrown.
     *
     * @param index the 0-based index of the method's instruction list
     * @return the instruction at <tt>index</tt>
     */
    public Instruction getInstructionAt( int index )
    {
        // even though the list allows us to get the "last" instruction,
        // don't allow the user to do this.
        if (index == getInstructionCount())
        {
            throw new IndexOutOfBoundsException(
                "Even though you can put a mark at one more than the last "+
                "instruction, you cannot retrieve any such instruction." );
        }
        
        MarkedInstruction mi = this.list.getInstructionAt( index );
        return mi.getInstruction();
    }
    
    
    /**
     * Marks an instruction for coverage analysis.  If the index
     * is out of range (&lt; 0 or &gt; <tt>getInstructionCount()</tt>),
     * then a <tt>IndexOutOfBoundsException</tt> is thrown.  Marks are
     * added before the instruction at the given index is executed. Note that
     * to mark the end of the method, you should use index equal to
     * <tt>getInstructionCount()</tt>
     *
     * @param index the 0-based index of the method's instruction list
     * @param meta meta-data the analysis module records in association with
     *             the mark.  This cannot be <tt>null</tt>.
     */
    public synchronized void markInstruction( int index,
            IAnalysisMetaData meta )
    {
        if (meta == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        short count = this.markCount;
        ++this.markCount;
        
        LOG.debug( "Mark "+this+" at instruction "+index+"." );
        
        // first, mark the instruction
        MarkedInstruction mi = this.list.getInstructionAt( index );
        mi.addMark( this.measureIndex, count );
        
        
        MarkRecord mr = new MarkRecord( meta,
            this.analysisModule.getMeasureName(), getMethodName(), count,
            getSourceLine( mi ) );
        cr.addMark( mr );
    }
    
    
    /**
     * Output a friendly version of the object.
     */
    public String toString()
    {
        return getClassName() + "#" + getMethodName();
    }
    
    
    private int getSourceLine( MarkedInstruction mi )
    {
        int ret = -1;
        LineNumberTable lnt = getLineNumberTable();
        if (lnt != null)
        {
            // see bug 938439
            // Fix by Stefano Turri
            try
            {
                ret = lnt.getSourceLine( mi.getInstructionPosition() );
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
                ret = -1;
            }
        }
        return ret;
    }
}

