/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.cf.code;

import com.android.dx.cf.attrib.AttCode;
import com.android.dx.cf.attrib.AttLineNumberTable;
import com.android.dx.cf.attrib.AttLocalVariableTable;
import com.android.dx.cf.attrib.AttLocalVariableTypeTable;
import com.android.dx.cf.iface.AttributeList;
import com.android.dx.cf.iface.ClassFile;
import com.android.dx.cf.iface.Method;
import com.android.dx.rop.code.AccessFlags;
import com.android.dx.rop.code.SourcePosition;
import com.android.dx.rop.cst.CstNat;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.Prototype;

/**
 * Container for all the giblets that make up a concrete Java bytecode method.
 * It implements {@link Method}, so it provides all the original access
 * (by delegation), but it also constructs and keeps useful versions of
 * stuff extracted from the method's {@code Code} attribute.
 */
public final class ConcreteMethod implements Method {
    /** {@code non-null;} method being wrapped */
    private final Method method;

    /** {@code non-null;} the {@code ClassFile} the method belongs to. */
    private final ClassFile classFile;

    /** {@code non-null;} the code attribute */
    private final AttCode attCode;

    /** {@code non-null;} line number list */
    private final LineNumberList lineNumbers;

    /** {@code non-null;} local variable list */
    private final LocalVariableList localVariables;

    /**
     * Constructs an instance.
     *
     * @param method {@code non-null;} the method to be based on
     * @param classFile {@code non-null;} the class file that contains this method
     * @param keepLines whether to keep the line number information
     * (if any)
     * @param keepLocals whether to keep the local variable
     * information (if any)
     */
    public ConcreteMethod(Method method, ClassFile classFile,
            boolean keepLines, boolean keepLocals) {
        this.method = method;
        this.classFile = classFile;

        AttributeList attribs = method.getAttributes();
        this.attCode = (AttCode) attribs.findFirst(AttCode.ATTRIBUTE_NAME);

        AttributeList codeAttribs = attCode.getAttributes();

        /*
         * Combine all LineNumberTable attributes into one, with the
         * combined result saved into the instance. The following code
         * isn't particularly efficient for doing merges, but as far
         * as I know, this situation rarely occurs "in the
         * wild," so there's not much point in optimizing for it.
         */
        LineNumberList lnl = LineNumberList.EMPTY;
        if (keepLines) {
            for (AttLineNumberTable lnt = (AttLineNumberTable)
                     codeAttribs.findFirst(AttLineNumberTable.ATTRIBUTE_NAME);
                 lnt != null;
                 lnt = (AttLineNumberTable) codeAttribs.findNext(lnt)) {
                lnl = LineNumberList.concat(lnl, lnt.getLineNumbers());
            }
        }
        this.lineNumbers = lnl;

        LocalVariableList lvl = LocalVariableList.EMPTY;
        if (keepLocals) {
            /*
             * Do likewise (and with the same caveat) for
             * LocalVariableTable and LocalVariableTypeTable attributes.
             * This combines both of these kinds of attribute into a
             * single LocalVariableList.
             */
            for (AttLocalVariableTable lvt = (AttLocalVariableTable)
                     codeAttribs.findFirst(
                             AttLocalVariableTable.ATTRIBUTE_NAME);
                 lvt != null;
                 lvt = (AttLocalVariableTable) codeAttribs.findNext(lvt)) {

                lvl = LocalVariableList.concat(lvl, lvt.getLocalVariables());
            }

            LocalVariableList typeList = LocalVariableList.EMPTY;
            for (AttLocalVariableTypeTable lvtt = (AttLocalVariableTypeTable)
                     codeAttribs.findFirst(
                             AttLocalVariableTypeTable.ATTRIBUTE_NAME);
                 lvtt != null;
                 lvtt = (AttLocalVariableTypeTable) codeAttribs.findNext(lvtt)) {
                typeList = LocalVariableList.concat(typeList, lvtt.getLocalVariables());
            }

            if (typeList.size() != 0) {

                lvl = LocalVariableList.mergeDescriptorsAndSignatures(lvl, typeList);
            }
        }
        this.localVariables = lvl;
    }


    /**
     * Gets the source file associated with the method if known.
     * @return {null-ok;} the source file defining the method if known, null otherwise.
     */
    public CstString getSourceFile() {
        return classFile.getSourceFile();
    }

    /**
     * Tests whether the method is being defined on an interface.
     * @return true if the method is being defined on an interface.
     */
    public final boolean isDefaultOrStaticInterfaceMethod() {
        return (classFile.getAccessFlags() & AccessFlags.ACC_INTERFACE) != 0
            && !getNat().isClassInit();
    }

    /**
     * Tests whether the method is being defined is declared as static.
     * @return true if the method is being defined is declared as static.
     */
    public final boolean isStaticMethod() {
        return (getAccessFlags() & AccessFlags.ACC_STATIC) != 0;
    }

    /** {@inheritDoc} */
    @Override
    public CstNat getNat() {
        return method.getNat();
    }

    /** {@inheritDoc} */
    @Override
    public CstString getName() {
        return method.getName();
    }

    /** {@inheritDoc} */
    @Override
    public CstString getDescriptor() {
        return method.getDescriptor();
    }

    /** {@inheritDoc} */
    @Override
    public int getAccessFlags() {
        return method.getAccessFlags();
    }

    /** {@inheritDoc} */
    @Override
    public AttributeList getAttributes() {
        return method.getAttributes();
    }

    /** {@inheritDoc} */
    @Override
    public CstType getDefiningClass() {
        return method.getDefiningClass();
    }

    /** {@inheritDoc} */
    @Override
    public Prototype getEffectiveDescriptor() {
        return method.getEffectiveDescriptor();
    }

    /**
     * Gets the maximum stack size.
     *
     * @return {@code >= 0;} the maximum stack size
     */
    public int getMaxStack() {
        return attCode.getMaxStack();
    }

    /**
     * Gets the number of locals.
     *
     * @return {@code >= 0;} the number of locals
     */
    public int getMaxLocals() {
        return attCode.getMaxLocals();
    }

    /**
     * Gets the bytecode array.
     *
     * @return {@code non-null;} the bytecode array
     */
    public BytecodeArray getCode() {
        return attCode.getCode();
    }

    /**
     * Gets the exception table.
     *
     * @return {@code non-null;} the exception table
     */
    public ByteCatchList getCatches() {
        return attCode.getCatches();
    }

    /**
     * Gets the line number list.
     *
     * @return {@code non-null;} the line number list
     */
    public LineNumberList getLineNumbers() {
        return lineNumbers;
    }

    /**
     * Gets the local variable list.
     *
     * @return {@code non-null;} the local variable list
     */
    public LocalVariableList getLocalVariables() {
        return localVariables;
    }

    /**
     * Returns a {@link SourcePosition} instance corresponding to the
     * given bytecode offset.
     *
     * @param offset {@code >= 0;} the bytecode offset
     * @return {@code non-null;} an appropriate instance
     */
    public SourcePosition makeSourcePosistion(int offset) {
        return new SourcePosition(getSourceFile(), offset,
                                  lineNumbers.pcToLine(offset));
    }
}
