/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

package sun.jvm.hotspot.oops;

import java.io.*;
import java.util.*;
import sun.jvm.hotspot.code.*;
import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.interpreter.*;
import sun.jvm.hotspot.memory.*;
import sun.jvm.hotspot.runtime.*;
import sun.jvm.hotspot.types.*;
import sun.jvm.hotspot.utilities.*;

public class ConstMethod extends VMObject {
  static {
    VM.registerVMInitializedObserver(new Observer() {
        public void update(Observable o, Object data) {
          initialize(VM.getVM().getTypeDataBase());
        }
      });
  }

  // anon-enum constants for _flags.
  private static int HAS_LINENUMBER_TABLE;
  private static int HAS_CHECKED_EXCEPTIONS;
  private static int HAS_LOCALVARIABLE_TABLE;
  private static int HAS_EXCEPTION_TABLE;
  private static int HAS_GENERIC_SIGNATURE;
  private static int HAS_METHOD_ANNOTATIONS;
  private static int HAS_PARAMETER_ANNOTATIONS;
  private static int HAS_METHOD_PARAMETERS;
  private static int HAS_DEFAULT_ANNOTATIONS;
  private static int HAS_TYPE_ANNOTATIONS;

  private static final int sizeofShort = 2;

  private static synchronized void initialize(TypeDataBase db) throws WrongTypeException {
    Type type                  = db.lookupType("ConstMethod");
    constants                  = new MetadataField(type.getAddressField("_constants"), 0);
    constMethodSize            = new CIntField(type.getCIntegerField("_constMethod_size"), 0);
    flags                      = new CIntField(type.getCIntegerField("_flags"), 0);

    // enum constants for flags
    HAS_LINENUMBER_TABLE      = db.lookupIntConstant("ConstMethod::_has_linenumber_table").intValue();
    HAS_CHECKED_EXCEPTIONS     = db.lookupIntConstant("ConstMethod::_has_checked_exceptions").intValue();
    HAS_LOCALVARIABLE_TABLE   = db.lookupIntConstant("ConstMethod::_has_localvariable_table").intValue();
    HAS_EXCEPTION_TABLE       = db.lookupIntConstant("ConstMethod::_has_exception_table").intValue();
    HAS_GENERIC_SIGNATURE     = db.lookupIntConstant("ConstMethod::_has_generic_signature").intValue();
    HAS_METHOD_ANNOTATIONS    = db.lookupIntConstant("ConstMethod::_has_method_annotations").intValue();
    HAS_PARAMETER_ANNOTATIONS = db.lookupIntConstant("ConstMethod::_has_parameter_annotations").intValue();
    HAS_METHOD_PARAMETERS = db.lookupIntConstant("ConstMethod::_has_method_parameters").intValue();
    HAS_DEFAULT_ANNOTATIONS   = db.lookupIntConstant("ConstMethod::_has_default_annotations").intValue();
    HAS_TYPE_ANNOTATIONS      = db.lookupIntConstant("ConstMethod::_has_type_annotations").intValue();

    // Size of Java bytecodes allocated immediately after ConstMethod*.
    codeSize                   = new CIntField(type.getCIntegerField("_code_size"), 0);
    nameIndex                  = new CIntField(type.getCIntegerField("_name_index"), 0);
    signatureIndex             = new CIntField(type.getCIntegerField("_signature_index"), 0);
    idnum                      = new CIntField(type.getCIntegerField("_method_idnum"), 0);
    maxStack                   = new CIntField(type.getCIntegerField("_max_stack"), 0);
    maxLocals                  = new CIntField(type.getCIntegerField("_max_locals"), 0);
    sizeOfParameters           = new CIntField(type.getCIntegerField("_size_of_parameters"), 0);

    // start of byte code
    bytecodeOffset = type.getSize();

    type                       = db.lookupType("MethodParametersElement");
    methodParametersElementSize = type.getSize();

    type                       = db.lookupType("CheckedExceptionElement");
    checkedExceptionElementSize = type.getSize();

    type                       = db.lookupType("LocalVariableTableElement");
    localVariableTableElementSize = type.getSize();

    type                       = db.lookupType("ExceptionTableElement");
    exceptionTableElementSize = type.getSize();
  }

  public ConstMethod(Address addr) {
    super(addr);
  }

  // Fields
  private static MetadataField constants;
  private static CIntField constMethodSize;
  private static CIntField flags;
  private static CIntField codeSize;
  private static CIntField nameIndex;
  private static CIntField signatureIndex;
  private static CIntField idnum;
  private static CIntField maxStack;
  private static CIntField maxLocals;
  private static CIntField sizeOfParameters;

  // start of bytecode
  private static long bytecodeOffset;
  private static long methodParametersElementSize;
  private static long checkedExceptionElementSize;
  private static long localVariableTableElementSize;
  private static long exceptionTableElementSize;

  public Method getMethod() {
    InstanceKlass ik = (InstanceKlass)getConstants().getPoolHolder();
    MethodArray methods = ik.getMethods();
    return methods.at((int)getIdNum());
  }

  // Accessors for declared fields
  public ConstantPool getConstants() {
    return (ConstantPool) constants.getValue(this);
  }

  public long getConstMethodSize() {
    return constMethodSize.getValue(this);
  }

  public long getFlags() {
    return flags.getValue(this);
  }

  public long getCodeSize() {
    return codeSize.getValue(this);
  }

  public long getNameIndex() {
    return nameIndex.getValue(this);
  }

  public long getSignatureIndex() {
    return signatureIndex.getValue(this);
  }

  public long getGenericSignatureIndex() {
    if (hasGenericSignature()) {
      return getAddress().getCIntegerAt(offsetOfGenericSignatureIndex(), 2, true);
    } else {
      return 0;
    }
  }

  public long getIdNum() {
    return idnum.getValue(this);
  }

  public long getMaxStack() {
    return maxStack.getValue(this);
  }

  public long getMaxLocals() {
    return maxLocals.getValue(this);
  }

  public long getSizeOfParameters() {
    return sizeOfParameters.getValue(this);
  }

  public Symbol getName() {
    return getMethod().getName();
  }

  public Symbol getSignature() {
    return getMethod().getSignature();
  }

  public Symbol getGenericSignature() {
    return getMethod().getGenericSignature();
  }

  // bytecode accessors

  /** Get a bytecode or breakpoint at the given bci */
  public int getBytecodeOrBPAt(int bci) {
    return getAddress().getJByteAt(bytecodeOffset + bci) & 0xFF;
  }

  public byte getBytecodeByteArg(int bci) {
    return (byte) getBytecodeOrBPAt(bci);
  }

  /** Fetches a 16-bit big-endian ("Java ordered") value from the
      bytecode stream */
  public short getBytecodeShortArg(int bci) {
    int hi = getBytecodeOrBPAt(bci);
    int lo = getBytecodeOrBPAt(bci + 1);
    return (short) ((hi << 8) | lo);
  }

  /** Fetches a 16-bit native ordered value from the
      bytecode stream */
  public short getNativeShortArg(int bci) {
    int hi = getBytecodeOrBPAt(bci);
    int lo = getBytecodeOrBPAt(bci + 1);
    if (VM.getVM().isBigEndian()) {
        return (short) ((hi << 8) | lo);
    } else {
        return (short) ((lo << 8) | hi);
    }
  }

  /** Fetches a 32-bit big-endian ("Java ordered") value from the
      bytecode stream */
  public int getBytecodeIntArg(int bci) {
    int b4 = getBytecodeOrBPAt(bci);
    int b3 = getBytecodeOrBPAt(bci + 1);
    int b2 = getBytecodeOrBPAt(bci + 2);
    int b1 = getBytecodeOrBPAt(bci + 3);

    return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
  }

  /** Fetches a 32-bit native ordered value from the
      bytecode stream */
  public int getNativeIntArg(int bci) {
    int b4 = getBytecodeOrBPAt(bci);
    int b3 = getBytecodeOrBPAt(bci + 1);
    int b2 = getBytecodeOrBPAt(bci + 2);
    int b1 = getBytecodeOrBPAt(bci + 3);

    if (VM.getVM().isBigEndian()) {
        return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
    } else {
        return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
    }
  }

  public byte[] getByteCode() {
     byte[] bc = new byte[ (int) getCodeSize() ];
     for( int i=0; i < bc.length; i++ )
     {
        long offs = bytecodeOffset + i;
        bc[i] = getAddress().getJByteAt( offs );
     }
     return bc;
  }

  public long getSize() {
    return getConstMethodSize();
  }

  public void printValueOn(PrintStream tty) {
    tty.print("ConstMethod " + getName().asString() + getSignature().asString() + "@" + getAddress());
  }

  public void iterateFields(MetadataVisitor visitor) {
    visitor.doMetadata(constants, true);
      visitor.doCInt(constMethodSize, true);
      visitor.doCInt(flags, true);
      visitor.doCInt(codeSize, true);
      visitor.doCInt(nameIndex, true);
      visitor.doCInt(signatureIndex, true);
      visitor.doCInt(codeSize, true);
      visitor.doCInt(maxStack, true);
      visitor.doCInt(maxLocals, true);
      visitor.doCInt(sizeOfParameters, true);
    }

  // Accessors

  public boolean hasLineNumberTable() {
    return (getFlags() & HAS_LINENUMBER_TABLE) != 0;
  }

  public int getLineNumberFromBCI(int bci) {
    if (!VM.getVM().isCore()) {
      if (bci == DebugInformationRecorder.SYNCHRONIZATION_ENTRY_BCI) bci = 0;
    }

    if (isNative()) {
      return -1;
    }

    if (Assert.ASSERTS_ENABLED) {
      Assert.that(bci == 0 || 0 <= bci && bci < getCodeSize(), "illegal bci");
    }
    int bestBCI  =  0;
    int bestLine = -1;
    if (hasLineNumberTable()) {
      // The line numbers are a short array of 2-tuples [start_pc, line_number].
      // Not necessarily sorted and not necessarily one-to-one.
      CompressedLineNumberReadStream stream =
        new CompressedLineNumberReadStream(getAddress(), (int) offsetOfCompressedLineNumberTable());
      while (stream.readPair()) {
        if (stream.bci() == bci) {
          // perfect match
          return stream.line();
        } else {
          // update best_bci/line
          if (stream.bci() < bci && stream.bci() >= bestBCI) {
            bestBCI  = stream.bci();
            bestLine = stream.line();
          }
        }
      }
    }
    return bestLine;
  }

  public LineNumberTableElement[] getLineNumberTable() {
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(hasLineNumberTable(),
                  "should only be called if table is present");
    }
    int len = getLineNumberTableLength();
    CompressedLineNumberReadStream stream =
      new CompressedLineNumberReadStream(getAddress(), (int) offsetOfCompressedLineNumberTable());
    LineNumberTableElement[] ret = new LineNumberTableElement[len];

    for (int idx = 0; idx < len; idx++) {
      stream.readPair();
      ret[idx] = new LineNumberTableElement(stream.bci(), stream.line());
    }
    return ret;
  }

  public boolean hasLocalVariableTable() {
    return (getFlags() & HAS_LOCALVARIABLE_TABLE) != 0;
  }

  public Symbol getLocalVariableName(int bci, int slot) {
    return getMethod().getLocalVariableName(bci, slot);
  }

  /** Should only be called if table is present */
  public LocalVariableTableElement[] getLocalVariableTable() {
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(hasLocalVariableTable(), "should only be called if table is present");
    }
    LocalVariableTableElement[] ret = new LocalVariableTableElement[getLocalVariableTableLength()];
    long offset = offsetOfLocalVariableTable();
    for (int i = 0; i < ret.length; i++) {
      ret[i] = new LocalVariableTableElement(getAddress(), offset);
      offset += localVariableTableElementSize;
    }
    return ret;
  }

  public boolean hasExceptionTable() {
    return (getFlags() & HAS_EXCEPTION_TABLE) != 0;
  }

  public ExceptionTableElement[] getExceptionTable() {
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(hasExceptionTable(), "should only be called if table is present");
    }
    ExceptionTableElement[] ret = new ExceptionTableElement[getExceptionTableLength()];
    long offset = offsetOfExceptionTable();
    for (int i = 0; i < ret.length; i++) {
      ret[i] = new ExceptionTableElement(getAddress(), offset);
      offset += exceptionTableElementSize;
    }
    return ret;
  }

  public boolean hasCheckedExceptions() {
    return (getFlags() & HAS_CHECKED_EXCEPTIONS) != 0;
  }

  public CheckedExceptionElement[] getCheckedExceptions() {
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(hasCheckedExceptions(), "should only be called if table is present");
    }
    CheckedExceptionElement[] ret = new CheckedExceptionElement[getCheckedExceptionsLength()];
    long offset = offsetOfCheckedExceptions();
    for (int i = 0; i < ret.length; i++) {
      ret[i] = new CheckedExceptionElement(getAddress(), offset);
      offset += checkedExceptionElementSize;
    }
    return ret;
  }

  private boolean hasMethodParameters() {
    return (getFlags() & HAS_METHOD_PARAMETERS) != 0;
  }

  private boolean hasGenericSignature() {
    return (getFlags() & HAS_GENERIC_SIGNATURE) != 0;
  }

  private boolean hasMethodAnnotations() {
    return (getFlags() & HAS_METHOD_ANNOTATIONS) != 0;
  }

  private boolean hasParameterAnnotations() {
    return (getFlags() & HAS_PARAMETER_ANNOTATIONS) != 0;
  }

  private boolean hasDefaultAnnotations() {
    return (getFlags() & HAS_DEFAULT_ANNOTATIONS) != 0;
  }

  private boolean hasTypeAnnotations() {
    return (getFlags() & HAS_TYPE_ANNOTATIONS) != 0;
  }


  //---------------------------------------------------------------------------
  // Internals only below this point
  //

  private boolean isNative() {
    return getMethod().isNative();
  }

  // Offset of end of code
  private long offsetOfCodeEnd() {
    return bytecodeOffset + getCodeSize();
  }

  // Offset of start of compressed line number table (see method.hpp)
  private long offsetOfCompressedLineNumberTable() {
    return offsetOfCodeEnd() + (isNative() ? 2 * VM.getVM().getAddressSize() : 0);
  }

  // Offset of last short in Method* before annotations, if present
  private long offsetOfLastU2Element() {
    int offset = 0;
    if (hasMethodAnnotations()) offset++;
    if (hasParameterAnnotations()) offset++;
    if (hasTypeAnnotations()) offset++;
    if (hasDefaultAnnotations()) offset++;
    long wordSize = VM.getVM().getObjectHeap().getOopSize();
    return (getSize() * wordSize) - (offset * wordSize) - sizeofShort;
  }

  // Offset of the generic signature index
  private long offsetOfGenericSignatureIndex() {
    return offsetOfLastU2Element();
  }

  private long offsetOfMethodParametersLength() {
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(hasMethodParameters(), "should only be called if table is present");
    }
    return hasGenericSignature() ? offsetOfLastU2Element() - sizeofShort :
                                   offsetOfLastU2Element();
  }

  private int getMethodParametersLength() {
      if (hasMethodParameters())
          return (int) getAddress().getCIntegerAt(offsetOfMethodParametersLength(), 2, true);
      else
          return 0;
  }

  // Offset of start of checked exceptions
  private long offsetOfMethodParameters() {
    long offset = offsetOfMethodParametersLength();
    long length = getMethodParametersLength();
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(length > 0, "should only be called if method parameter information is present");
    }
    offset -= length * methodParametersElementSize;
    return offset;
  }

  private long offsetOfCheckedExceptionsLength() {
    if (hasMethodParameters())
      return offsetOfMethodParameters() - sizeofShort;
    else {
      return hasGenericSignature() ? offsetOfLastU2Element() - sizeofShort :
                                     offsetOfLastU2Element();
    }
  }

  private int getCheckedExceptionsLength() {
    if (hasCheckedExceptions()) {
      return (int) getAddress().getCIntegerAt(offsetOfCheckedExceptionsLength(), 2, true);
    } else {
      return 0;
    }
  }

  // Offset of start of checked exceptions
  private long offsetOfCheckedExceptions() {
    long offset = offsetOfCheckedExceptionsLength();
    long length = getCheckedExceptionsLength();
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(length > 0, "should only be called if table is present");
    }
    offset -= length * checkedExceptionElementSize;
    return offset;
  }

  private int getLineNumberTableLength() {
    int len = 0;
    if (hasLineNumberTable()) {
      CompressedLineNumberReadStream stream =
        new CompressedLineNumberReadStream(getAddress(), (int) offsetOfCompressedLineNumberTable());
      while (stream.readPair()) {
        len += 1;
      }
    }
    return len;
  }

  private int getLocalVariableTableLength() {
    if (hasLocalVariableTable()) {
      return (int) getAddress().getCIntegerAt(offsetOfLocalVariableTableLength(), 2, true);
    } else {
      return 0;
    }
  }

  // Offset of local variable table length
  private long offsetOfLocalVariableTableLength() {
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(hasLocalVariableTable(), "should only be called if table is present");
    }

    if (hasExceptionTable()) {
      return offsetOfExceptionTable() - sizeofShort;
    } else if (hasCheckedExceptions()) {
      return offsetOfCheckedExceptions() - sizeofShort;
    } else if (hasMethodParameters()) {
      return offsetOfMethodParameters() - sizeofShort;
    } else {
      return hasGenericSignature() ? offsetOfLastU2Element() - sizeofShort :
                                     offsetOfLastU2Element();
    }
  }

  private long offsetOfLocalVariableTable() {
    long offset = offsetOfLocalVariableTableLength();
    long length = getLocalVariableTableLength();
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(length > 0, "should only be called if table is present");
    }
    offset -= length * localVariableTableElementSize;
    return offset;
  }

  private int getExceptionTableLength() {
    if (hasExceptionTable()) {
      return (int) getAddress().getCIntegerAt(offsetOfExceptionTableLength(), 2, true);
    } else {
      return 0;
    }
  }

  private long offsetOfExceptionTableLength() {
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(hasExceptionTable(), "should only be called if table is present");
    }
    if (hasCheckedExceptions()) {
      return offsetOfCheckedExceptions() - sizeofShort;
    } else if (hasMethodParameters()) {
      return offsetOfMethodParameters() - sizeofShort;
    } else {
      return hasGenericSignature() ? offsetOfLastU2Element() - sizeofShort :
                                     offsetOfLastU2Element();
    }
  }

  private long offsetOfExceptionTable() {
    long offset = offsetOfExceptionTableLength();
    long length = getExceptionTableLength();
    if (Assert.ASSERTS_ENABLED) {
      Assert.that(length > 0, "should only be called if table is present");
    }
    offset -= length * exceptionTableElementSize;
    return offset;
  }

}
