/*
 * @(#)BytecodeLineUtil.java
 *
 * Copyright (C) 2003-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.module;


import java.util.Arrays;
import java.util.Comparator;

import net.sourceforge.groboutils.codecoverage.v2.IAnalysisMetaData;
import net.sourceforge.groboutils.codecoverage.v2.IMethodCode;

import org.apache.bcel.classfile.LineNumber;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;


/**
 * Helper that processes bytecode instructions and line-numbering
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @author    Juergen Kindler <a href="mailto:jkindler@freenet.de">jkindler@freenet.de</a>
 * @version   $Date: 2004/04/15 05:48:26 $
 * @since     February 18, 2003
 * @see       IAnalysisMetaData
 */
public class BytecodeLineUtil
{
    private int[] instructionPos; // bytecode postion for instruction index
    private int[] instructionLine; // linenumber for instruction index
    private InstructionHandle[] handles;
    
    private static final LineNumberComparator LINENUMBER_COMPARATOR =
        new LineNumberComparator();
    
    /**
     * Helper for sorting a line number table by program counter
     *
     * @author    Juergen Kindler <a href="mailto:jkindler@freenet.de">jkindler@freenet.de</a>
     */
    private static class LineNumberComparator implements Comparator
    {
        /**
         * Compares the PC of two LineNumber objects.
         * Other objects will lead to a ClassCastException.
         *
         * @param o1 an <code>Object</code> value
         * @param o2 an <code>Object</code> value
         * @return an <code>int</code> value
         */
        public int compare(Object o1, Object o2)
        {
            return ((LineNumber) o1).getStartPC() -
                ((LineNumber) o2).getStartPC();
        }
    
    
        /**
         * Compares equality of comparator objects.
         * Returns that objects of same class as this one to be equal to this.
         *
         * @param o an <code>Object</code> value
         * @return a <code>boolean</code> value
         */
        public boolean equals(Object o)
        {
            return (o != null) ? (this.getClass() == o.getClass()) : false;
        }
    }
    
    
    /**
     * 
     */
    public BytecodeLineUtil( Method m )
    {
        if (m == null)
        {
            throw new IllegalArgumentException("no null args.");
        }
        initialize( m );
    }
    
    
    public BytecodeLineUtil( IMethodCode m )
    {
        if (m == null)
        {
            throw new IllegalArgumentException("no null args.");
        }
        initialize( m.getOriginalMethod() );
    }
    
    
    public InstructionHandle[] getHandles()
    {
        return this.handles;
    }
    
    
    public int getLineNumber( InstructionHandle ih )
    {
        if (ih == null)
        {
            throw new IllegalArgumentException("no null args.");
        }
        return getLineNumberForBytecodePos( ih.getPosition() );
    }
    
    
    public int getLineNumberForBytecodePos( int bytecodePos )
    {
        int instrPos = getInstructionPosForBytecodePos( bytecodePos );
        return getLineNumberForInstructionPos( instrPos );
    }
    
    
    public int getLineNumberForInstructionPos( int instrPos )
    {
        if (instrPos >= 0 && instrPos < this.instructionLine.length)
        {
            return this.instructionLine[ instrPos ];
        }
        //else
        return -1;
    }
    
    
    public int getInstructionPosForBytecodePos( int bytecodePos )
    {
        // this method needs to account for the virtual instructions at the
        // end of a method.
        
        int len = this.instructionPos.length;
        if (len == 0)
        {
            return 0;
        }
        for (int i = 0; i < len; ++i)
        {
            if (this.instructionPos[i] == bytecodePos)
            {
                return i;
            }
        }
        if (len >= 1 && this.instructionPos[ len - 1 ] < bytecodePos )
        {
            return len;
        }
        
        throw new IllegalStateException( "Unknown bytecode position "+
            bytecodePos );
    }
    
    
    
    
    //-----------------------------------------------------------------------
    
    
    protected void initialize( Method m )
    {
        if (m == null)
        {
            throw new IllegalArgumentException("no null args.");
        }
        LineNumberTable lnt = m.getLineNumberTable();
        LineNumber[] lines = sort( lnt );
        this.instructionPos = getInstructionPositions( m );
        this.instructionLine = getInstructionLines( lines,
            this.instructionPos );
    }
    
    
    private LineNumber[] sort( LineNumberTable lnt )
    {
        if (lnt == null)
        {
            return new LineNumber[0];
        }
        LineNumber[] lines = lnt.getLineNumberTable();
        if (lines == null)
        {
            return new LineNumber[0];
        }
        
        Arrays.sort( lines, LINENUMBER_COMPARATOR );
        return lines;
    }
    
    
    private int[] getInstructionPositions( Method m )
    {
        InstructionList il = new InstructionList( m.getCode().getCode() );
        il.setPositions();
        this.handles = il.getInstructionHandles();
        
        int instructionCount = handles.length;
        int instructionPos[] = new int[ instructionCount ];
        
        // find the positions of the instructions in the bytecode
        for (int i = 0; i < instructionCount; ++i)
        {
            instructionPos[i] = this.handles[i].getPosition();
        }
        return instructionPos;
    }
    
    
    private int[] getInstructionLines( LineNumber lines[], int bytePos[] )
    {
        int out[] = new int[ bytePos.length ];
        for (int bIndex = 0; bIndex < bytePos.length; ++bIndex)
        {
            out[bIndex] = getLinenoForBytePos( lines, bytePos[ bIndex ] );
        }
        return out;
    }
    
    
    private int getLinenoForBytePos( LineNumber lines[], int bytePos )
    {
        if (lines.length <= 0)
        {
            return -1;
        }
        
        for (int i = 1; i < lines.length; ++i)
        {
            if (bytePos < lines[ i ].getStartPC())
            {
                return lines[i - 1].getLineNumber();
            }
        }
        return lines[ lines.length - 1 ].getLineNumber();
    }
}

