/*
 * @(#)ModifiedTargeters.java
 *
 * Copyright (C) 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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionTargeter;
import org.apache.bcel.generic.LineNumberGen;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;


/**
 * Keeps track of different target types, and updates them according to
 * the changes in the instructions.
 * <p>
 * This class exists due to a bug in the CodeException mod code in BCEL.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/04/15 05:48:25 $
 * @since     Jan 21, 2004, 2002
 */
class ModifiedTargeters
{
    private static final org.apache.log4j.Logger LOG =
        org.apache.log4j.Logger.getLogger( ModifiedTargeters.class );
    private MethodGen methGen;
    private Map startToCodeException = new HashMap();
    /* no longer needed
    private Map endToCodeException = new HashMap();
    */
    private Map handlerToCodeException = new HashMap();
    private List myCegs = new LinkedList();
    private InstructionHandle lastInstruction;
    
    private static class MyCEG
    {
        private InstructionHandle origStartPC;
        private InstructionHandle newStartPC;
        
        private InstructionHandle origEndPC;
        private InstructionHandle newEndPC;
        
        private InstructionHandle origHandlerPC;
        private InstructionHandle newHandlerPC;
        
        private ObjectType type;
        
        public MyCEG( InstructionHandle s, InstructionHandle e,
                InstructionHandle h, ObjectType t )
        {
            this.type = t;
            this.origStartPC = s;
            this.origEndPC = e;
            this.origHandlerPC = h;
            
            if (t == null)
            {
                LOG.debug( ">> Found null object type in CodeExceptionGen" );
            }
        }
        
        
        public CodeExceptionGen addCodeException( MethodGen mg )
        {
            InstructionHandle sp = this.newStartPC;
            InstructionHandle ep = this.newEndPC;
            InstructionHandle hp = this.newHandlerPC;
            
            if (sp == null)
            {
                sp = this.origStartPC;
            }
            if (ep == null)
            {
                ep = this.origEndPC;
            }
            if (hp == null)
            {
                hp = this.origHandlerPC;
            }
            LOG.debug( "Changing exception handler for type "+this.type+
                ": moved from (start = "+this.origStartPC+
                ", end = "+this.origEndPC+", handler = "+this.origHandlerPC+
                ") to (start = "+sp+", end = "+ep+", handler = "+hp+")" );
            return mg.addExceptionHandler( sp, ep, hp, this.type );
        }
        
        
        public void setStartPC( InstructionHandle h )
        {
            if (this.newStartPC == null)
            {
                LOG.debug( "Updating internal start PC for exception "+
                    this.type+" to point to "+h );
                this.newStartPC = h;
            }
        }
        
        /* no longer supported
        public void setEndPC( InstructionHandle h )
        {
            LOG.debug( "Updating internal end PC for exception "+
                this.type+" to point to "+h );
            this.newEndPC = h;
        }
        */
        
        
        public void setHandlerPC( InstructionHandle h )
        {
            if (this.newHandlerPC == null)
            {
                LOG.debug( "Updating internal handler PC for exception "+
                    this.type+" to point to "+h );
                this.newHandlerPC = h;
            }
        }
    }
    
    /**
     */
    ModifiedTargeters( MethodGen mg )
    {
        if (mg == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        this.methGen = mg;
        
        // setup the maps...
        CodeExceptionGen ceg[] = mg.getExceptionHandlers();
        for (int i = 0; i < ceg.length; ++i)
        {
            MyCEG ceg2 = new MyCEG( ceg[i].getStartPC(), ceg[i].getEndPC(),
                ceg[i].getHandlerPC(), ceg[i].getCatchType() );
            this.myCegs.add( ceg2 );
            putList( this.startToCodeException, ceg2.origStartPC, ceg2 );
            /* no longer supported
            putList( this.endToCodeException, ceg2.origEndPC, ceg2 );
            */
            putList( this.handlerToCodeException, ceg2.origHandlerPC, ceg2 );
        }
        
        // discover the last instruction in the method
        Iterator list = mg.getInstructionList().iterator();
        while (list.hasNext())
        {
            this.lastInstruction = (InstructionHandle)list.next();
        }
    }
    
    
    
    
    /**
     * Inserted a probe into the list.  "Inserting" means it goes before the
     * instruction that's being probed, so the "end" parts of targeters don't
     * need to be updated...
     */
    public void insertProbe( InstructionHandle instr, InstructionHandle probe )
    {
        // the original point that was being updated may not be what was
        // last referenced for updating handlers.
        // HOWEVER, the way probes are added is by always inserting them
        // before the code that needs to be probed.  That means, adding
        // probe A will redirect targeters to A, then adding probe B
        // will put B after A but before the instructions, so the targeters
        // MUST NOT be updated, otherwise probe A will not be targeted
        // correctly.
        
        
        InstructionTargeter [] its = instr.getTargeters();
        if (its != null)
        {
            for (int j = 0; j < its.length; j++)
            {
                LOG.debug( "Found for instruction "+instr+" targeter "+
                    its[j] );
                
                // don't call updateTargeters, as that is faulty, and doesn't
                // match what's really going on.  Also, that technique was
                // wrong when dealing with multiple probes added to the same
                // instruction, multiple times.
                
                if (its[j] instanceof LineNumberGen)
                {
                    LOG.debug( "Updating line number to point to probe "+
                        probe );
                    ((LineNumberGen)its[j]).setInstruction( probe );
                }
                else
                if (its[j] instanceof LocalVariableGen)
                {
                    // this definitely needs to be separated out, so that
                    // if start and end are the same instruction, the end
                    // doesn't point to the new probe only.
                    if (instr.equals( ((LocalVariableGen)its[j]).getStart() ))
                    {
                        LOG.debug(
                            "Updating local variable start to point to probe "+
                            probe );
                        ((LocalVariableGen)its[j]).setStart( probe );
                    }
                }
                else
                if (its[j] instanceof CodeExceptionGen)
                {
                    // ignore - we'll handle this separately
                    LOG.debug( "Ignoring CodeExceptionGen" );
                }
                else
                if (its[j] instanceof BranchInstruction)
                {
                    // here we need to perform an update, so that the
                    // "Select" table can correctly be updated.
                    LOG.debug( "Updating branch instruction to point to probe "+
                        probe );
                    ((BranchInstruction)its[j]).updateTarget(
                        instr, probe );
                }
                else
                {
                    throw new IllegalStateException(
                        "Unexpected targeter type (" +
                        its[j].getClass().getName() +
                        ") for instruction " + instr );
                }
            }
        }
        
        // if the inserted probe points to the start of a code exception,
        // then redirect the code exception to the probe.
        Iterator iter = getList( this.startToCodeException, instr );
        while (iter.hasNext())
        {
            MyCEG mceg = (MyCEG)iter.next();
            mceg.setStartPC( probe );
        }
        
        // if the inserted probe points to the start of an exception handler,
        // then redirect the code exception.
        iter = getList( this.handlerToCodeException, instr );
        while (iter.hasNext())
        {
            MyCEG mceg = (MyCEG)iter.next();
            mceg.setHandlerPC( probe );
        }
    }
    
    
    /*
     * Here, we only need to update the end probes.  Note that the last
     * instruction in the method will change with these added probes.
     *
     * WARNING!!!! This should probably be pulled out, as it's very dangerous
     * to add a probe at the end of a method - it will occur after
     * any returns!
    public void appendProbe( InstructionHandle probe )
    {
        // get new last instruction
        InstructionHandle lastProbe = probe;
        while (lastProbe.getNext() != null)
        {
            lastProbe = lastProbe.getNext();
        }
        
        
        InstructionTargeter [] its = this.lastInstruction.getTargeters();
        if (its != null)
        {
            for (int j = 0; j < its.length; j++)
            {
                // don't call updateTargeters, as that is faulty, and doesn't
                // match what's really going on.  Also, that technique was
                // wrong when dealing with multiple probes added to the same
                // instruction, multiple times.
                
                if (its[j] instanceof LineNumberGen)
                {
                    // don't do anything
                }
                else
                if (its[j] instanceof LocalVariableGen)
                {
                    // this definitely needs to be separated out, so that
                    // if start and end are the same instruction, the start
                    // doesn't point to the new probe only.
                    if (this.lastInstruction.equals(
                        ((LocalVariableGen)its[j]).getEnd() ))
                    {
                        ((LocalVariableGen)its[j]).setEnd( lastProbe );
                    }
                }
                else
                if (its[j] instanceof CodeExceptionGen)
                {
                    // ignore - we'll handle this separately
                }
                else
                if (its[j] instanceof BranchInstruction)
                {
                    // don't do anything
                }
                else
                {
                    throw new IllegalStateException(
                        "Unexpected targeter type (" +
                        its[j].getClass().getName() +
                        ") for instruction " + this.lastInstruction );
                }
            }
        }
        
        // if the inserted probe points to the start of a code exception,
        // then redirect the code exception to the probe.
        Iterator iter = getList( this.endToCodeException, this.lastInstruction );
        while (iter.hasNext())
        {
            MyCEG mceg = (MyCEG)iter.next();
            mceg.setEndPC( lastProbe );
        }
        
        // update the last instruction pointer
        this.lastInstruction = lastProbe;
    }
     */
    
    
    /**
     * Updates the targeters after a series of insert and appends.
     * Note that the targeters might be updated during the inserts and
     * appends.
     */
    public void update()
    {
        this.methGen.removeExceptionHandlers();
        Iterator iter = this.myCegs.iterator();
        while (iter.hasNext())
        {
            ((MyCEG)iter.next()).addCodeException( this.methGen );
        }
    }
    
    
    private void putList( Map map, Object key, Object value )
    {
        Set s = (Set)map.get( key );
        if (s == null)
        {
            s = new HashSet();
            map.put( key, s );
        }
        s.add( value );
    }
    
    
    /**
     * Never returns null.
     */
    private Iterator getList( Map map, Object key )
    {
        Set s = (Set)map.get( key );
        if (s == null)
        {
            s = new HashSet();
        }
        return s.iterator();
    }
}

