/*
* JBoss, Home of Professional Open Source
* Copyright 2009-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.agent.adapter.cfg;

import org.objectweb.asm.Label;

import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;

/**
 * auxiliary used by CFG to store details of a specific try catch block
 */
public class TryCatchDetails
{
    /**
     * back link to the control flow graph
     */
    private CFG cfg;
    /**
     * the label identifying the start of the try catch block
     */
    private Label start;
    /**
     * the label identifying the end of the try catch block
     */
    private Label end;
    /**
     * the label identifying the start of the try catch block handler
     */
    private Label handler;
    /**
     * a list of monitor enter instructions which are opened within the scope of this try catch block
     * and hence which may require closing in the associated handler
     */
    private List<CodeLocation> openEnters;
    /**
     * the name of the exception type handled by the handler or null if it is a catch all handler
     */
    private String type;
    /**
     * true if this is a trigger handler otherwise false
     */
    private boolean isTriggerHandler;

    /**
     * A list of details for all the try catch regions which shadow this region i.e. which prevent exception
     * control flow to it from an embedded region because they either catch everything or they catch the
     * same type or a supertype of this exception.
     */
    private List<TryCatchDetails> shadowRegions;

    /**
     * construct a try catch details instance
     * @param cfg the control flow graph
     * @param start the try block start
     * @param end the try block end
     * @param handler the handler block start
     * @param type the handled exception type
     * @param isTriggerHandler true if this handler handles Byteman errors
     */
    public TryCatchDetails(CFG cfg, Label start, Label end, Label handler, String type, boolean isTriggerHandler)
    {
        this.cfg = cfg;
        this.start = start;
        this.end = end;
        this.handler = handler;
        this.openEnters = new LinkedList<CodeLocation>();
        this.type = type;
        this.isTriggerHandler = isTriggerHandler;
        this.shadowRegions  = null;
    }

    // accessors

    public Label getStart() {
        return start;
    }

    public Label getEnd() {
        return end;
    }

    public Label getHandler() {
        return handler;
    }

    public String getType() {
        return type;
    }

    public boolean isTriggerHandler() {
        return isTriggerHandler;
    }

    /**
     * add a new monitor enter location to the list of open locations associated with this handler
     * maintaining the reverse position ordering
     * @param openEnter the location of the monitor enter
     */
    public void addOpenEnter(CodeLocation openEnter)
    {
        Iterator<CodeLocation> iterator = openEnters.iterator();
        int pos = 0;
        while (iterator.hasNext()) {
            CodeLocation next = iterator.next();
            int compare = openEnter.compareTo(next);
            if (compare < 0) {
                // need to insert after this one
                pos++;
            } else if (compare > 0) {
                // need to insert before this one
                break;
            } else {
                // already in list!
                return;
            }
        }

        openEnters.add(pos, openEnter);
    }

    /**
     * check if a monitor enter location belongs to the list of open locations associated with this handler
     * @param openEnter the location of the monitor enter
     * @return true if it belongs ot the list
     */
    public boolean containsOpenEnter(CodeLocation openEnter)
    {
        return openEnters.contains(openEnter);
    }

    /**
     * add all the open locations associated with this handler to the supplied list of open locations
     * maintaining the reverse position ordering
     * @param openMonitorEnters list of locations of the monitor enters
     */
    public void addOpenLocations(List<CodeLocation> openMonitorEnters) {
        Iterator<CodeLocation> iterator = openEnters.iterator();
        while (iterator.hasNext()) {
            CodeLocation location = iterator.next();
            Iterator<CodeLocation> iterator2 = openMonitorEnters.iterator();
            int pos = 0;
            while (iterator2.hasNext()) {
                CodeLocation next = iterator2.next();
                int compare = location.compareTo(next);
                if (compare < 0) {
                    // need to insert after this one
                    pos++;
                } else {
                    // need to insert before this one unless it is already present
                    if (compare == 0) {
                        pos = -1;
                    }
                    break;
                }
            }

            if (pos >= 0) {
                openMonitorEnters.add(pos, location);
            }
        }
    }

    public Iterator<CodeLocation> getOpenEnters()
    {
        return openEnters.iterator();
    }

    /**
     * add a shadowing region to the list of regions which shadow this one
     * @param tryCatchDetails detaisl of a try catch block
     */
    public void addShadowRegion(TryCatchDetails tryCatchDetails)
    {
        if (shadowRegions == null) {
            shadowRegions = new LinkedList<TryCatchDetails>();
        }
        if (!shadowRegions.contains(tryCatchDetails)) {
            shadowRegions.add(tryCatchDetails);
        }
    }

    public List<TryCatchDetails> getShadowRegions()
    {
        return shadowRegions;
    }

    public boolean hasShadowRegion(TryCatchDetails tryCatchDetails)
    {
        return (shadowRegions != null && shadowRegions.contains(tryCatchDetails));
    }

    public String toString()
    {
        StringBuilder builder = new StringBuilder();
        builder.append("" + type + " try: " + cfg.getLocation(start) + " catch: " + cfg.getLocation(end) + " handler: " + cfg.getLocation(handler));
        return builder.toString();
    }
}
