/*
 * Bytecode Analysis Framework
 * Copyright (C) 2004, University of Maryland
 *
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import java.util.BitSet;

/**
 * Dataflow value representing the current nesting of catch and finally blocks.
 * We assume that any catch block with a non-empty catch type is a user catch
 * block, and any catch block with an empty catch type (i.e., catch all
 * exceptions) is a finally block. This assumption isn't quite accurate, but it
 * seems to be a reasonable first approximation.
 *
 * <p>
 * If valid (isValid() returns true), a BlockType value is a stack of elements,
 * which are either CATCH or FINALLY values. Call getDepth() to get the current
 * nesting depth. Call get(int <i>n</i>) to get the <i>n</i>th stack item. Call
 * getTopValue() to get the current top of the stack.
 * </p>
 *
 * <p>
 * If invalid (isValid() returns false), a BlockType value is either <i>top</i>
 * or <i>bottom</i>. These are the special values at the top and bottom of the
 * dataflow lattice.
 * </p>
 *
 * <p>
 * The dataflow lattice is effectively finite-height because real Java methods
 * are guaranteed to have a finite catch and finally block nesting level.
 * </p>
 *
 * @see BlockTypeAnalysis
 * @author David Hovemeyer
 */
public class BlockType extends BitSet {
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    public static final boolean CATCH = false;

    public static final boolean FINALLY = true;

    private boolean isValid;

    private boolean isTop;

    private int depth;

    /**
     * Constructor. Should only be called by BlockTypeAnalysis.
     */
    BlockType() {
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + depth;
        result = prime * result + (isTop ? 1231 : 1237);
        result = prime * result + (isValid ? 1231 : 1237);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (!(obj instanceof BlockType)) {
            return false;
        }
        final BlockType other = (BlockType) obj;
        if (depth != other.depth) {
            return false;
        }
        if (isTop != other.isTop) {
            return false;
        }
        if (isValid != other.isValid) {
            return false;
        }
        return true;
    }

    /**
     * Return whether or not this value is valid, meaning it contains a valid
     * representation of the nesting of catch and finally blocks.
     */
    public boolean isValid() {
        return isValid;
    }

    /**
     * Get the current nesting depth. The value must be valid.
     */
    public int getDepth() {
        if (!isValid) {
            throw new IllegalStateException();
        }
        return depth;
    }

    /**
     * Get the top value on the catch and finally block nesting stack.
     */
    public boolean getTopValue() {
        if (depth == 0) {
            throw new IllegalStateException();
        }
        return get(depth - 1);
    }

    /**
     * Return whether or not this value represents "normal" control-flow. Normal
     * control flow are all blocks outside any catch or finally block.
     */
    public boolean isNormal() {
        if (!isValid) {
            throw new IllegalStateException();
        }
        return getDepth() == 0;
    }

    /**
     * Make this value represent "normal" control flow.
     */
    public void setNormal() {
        this.isValid = true;
        this.depth = 0;
    }

    /**
     * Return whether or not this is the special "top" dataflow value.
     */
    public boolean isTop() {
        return !isValid && isTop;
    }

    /**
     * Make this the special "top" dataflow value.
     */
    public void setTop() {
        this.isValid = false;
        this.isTop = true;
    }

    /**
     * Return whether or not this is the special "bottom" dataflow value.
     */
    public boolean isBottom() {
        return !isValid && !isTop;
    }

    /**
     * Make this the special "bottom" dataflow value.
     */
    public void setBottom() {
        this.isValid = false;
        this.isTop = false;
    }

    /**
     * Make this object an exact duplicate of given object.
     *
     * @param other
     *            the other BlockType object
     */
    public void copyFrom(BlockType other) {
        this.isValid = other.isValid;
        this.isTop = other.isTop;
        if (isValid) {
            this.depth = other.depth;
            this.clear();
            this.or(other);
        }
    }

    /**
     * Return whether or not this object is identical to the one given.
     *
     * @param other
     *            the other BlockType object
     * @return true if this object is identical to the one given, false
     *         otherwise
     */
    public boolean sameAs(BlockType other) {
        if (!this.isValid) {
            return !other.isValid && (this.isTop == other.isTop);
        } else {
            if (!other.isValid) {
                return false;
            } else {
                // Both facts are valid
                if (this.depth != other.depth) {
                    return false;
                }

                // Compare bits
                for (int i = 0; i < this.depth; ++i) {
                    if (this.get(i) != other.get(i)) {
                        return false;
                    }
                }

                return true;
            }
        }
    }

    /**
     * Merge other dataflow value into this value.
     *
     * @param other
     *            the other BlockType value
     */
    public void mergeWith(BlockType other) {
        if (this.isTop() || other.isBottom()) {
            copyFrom(other);
        } else if (isValid()) {
            // To merge, we take the common prefix
            int pfxLen = Math.min(this.depth, other.depth);
            int commonLen;
            for (commonLen = 0; commonLen < pfxLen; ++commonLen) {
                if (this.get(commonLen) != other.get(commonLen)) {
                    break;
                }
            }
            this.depth = commonLen;
        }
    }

    /**
     * Enter a catch block.
     */
    public void pushCatch() {
        push(CATCH);
    }

    /**
     * Enter a finally block.
     */
    public void pushFinally() {
        push(FINALLY);
    }

    @Override
    public String toString() {
        if (isTop()) {
            return "<top>";
        } else if (isBottom()) {
            return "<bottom>";
        } else {
            StringBuilder buf = new StringBuilder();
            buf.append("N");
            for (int i = 0; i < depth; ++i) {
                buf.append(get(i) == CATCH ? "C" : "F");
            }
            return buf.toString();
        }
    }

    private void push(boolean value) {
        set(depth, value);
        ++depth;
    }
}

