/*
 * Copyright (c) 2000, 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.types.basic;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import sun.jvm.hotspot.debugger.Address;
import sun.jvm.hotspot.debugger.MachineDescription;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.types.Type;
import sun.jvm.hotspot.types.TypeDataBase;

/** <P> This is a basic implementation of the TypeDataBase interface.
    It allows an external type database builder to add types to be
    consumed by a client through the Type interfaces. It has no
    knowledge of symbol lookup; for example, the builder is
    responsible for providing the addresses of static fields. </P>

    <P> Among other things, the database builder is responsible for
    providing the Types for the Java primitive types, as well as their
    sizes. </P>
*/

public class BasicTypeDataBase implements TypeDataBase {
  private MachineDescription machDesc;
  private VtblAccess vtblAccess;
  /** Maps strings to Type objects. This does not contain the primitive types. */
  private Map nameToTypeMap = new HashMap();
  /** Maps strings to Integers, used for enums, etc. */
  private Map nameToIntConstantMap = new HashMap();
  /** Maps strings to Longs, used for 32/64-bit constants, etc. */
  private Map nameToLongConstantMap = new HashMap();
  /** Primitive types. */
  private Type jbooleanType;
  private Type jbyteType;
  private Type jcharType;
  private Type jdoubleType;
  private Type jfloatType;
  private Type jintType;
  private Type jlongType;
  private Type jshortType;

  /** For debugging */
  private static final boolean DEBUG;
  static {
    DEBUG = System.getProperty("sun.jvm.hotspot.types.basic.BasicTypeDataBase.DEBUG") != null;
  }

  public BasicTypeDataBase(MachineDescription machDesc, VtblAccess vtblAccess) {
    this.machDesc   = machDesc;
    this.vtblAccess = vtblAccess;
  }

  public Type lookupType(String cTypeName) {
    return lookupType(cTypeName, true);
  }

  public Type lookupType(String cTypeName, boolean throwException) {
    Type type = (Type) nameToTypeMap.get(cTypeName);
    if (type == null && throwException) {
      throw new RuntimeException("No type named \"" + cTypeName + "\" in database");
    }
    return type;
  }

  public Integer lookupIntConstant(String constantName) {
    return lookupIntConstant(constantName, true);
  }

  public Integer lookupIntConstant(String constantName, boolean throwException) {
    Integer i = (Integer) nameToIntConstantMap.get(constantName);
    if (i == null) {
      if (throwException) {
        throw new RuntimeException("No integer constant named \"" + constantName + "\" present in type database");
      }
    }
    return i;
  }

  public Long lookupLongConstant(String constantName) {
    return lookupLongConstant(constantName, true);
  }

  public Long lookupLongConstant(String constantName, boolean throwException) {
    Long i = (Long) nameToLongConstantMap.get(constantName);
    if (i == null) {
      if (throwException) {
        throw new RuntimeException("No long constant named \"" + constantName + "\" present in type database");
      }
    }
    return i;
  }

  public Type getJBooleanType() {
    return jbooleanType;
  }

  public Type getJByteType() {
    return jbyteType;
  }

  public Type getJCharType() {
    return jcharType;
  }

  public Type getJDoubleType() {
    return jdoubleType;
  }

  public Type getJFloatType() {
    return jfloatType;
  }

  public Type getJIntType() {
    return jintType;
  }

  public Type getJLongType() {
    return jlongType;
  }

  public Type getJShortType() {
    return jshortType;
  }

  public long getAddressSize() {
    return machDesc.getAddressSize();
  }

  public long getOopSize() {
    return VM.getVM().getOopSize();
  }

  HashMap typeToVtbl = new HashMap();

  private Address vtblForType(Type type) {
    Address vtblAddr = (Address)typeToVtbl.get(type);
    if (vtblAddr == null) {
      vtblAddr = vtblAccess.getVtblForType(type);
      if (vtblAddr != null) {
        typeToVtbl.put(type, vtblAddr);
      }
    }
    return vtblAddr;
  }

  public boolean addressTypeIsEqualToType(Address addr, Type type) {
    if (addr == null) {
      return false;
    }

    // This implementation should be suitably platform-independent; we
    // search nearby memory for the vtbl value of the given type.

    Address vtblAddr = vtblForType(type);

    if (vtblAddr == null) {
      // Type was not polymorphic, or an error occurred during lookup
      if (DEBUG) {
        System.err.println("BasicTypeDataBase.addressTypeIsEqualToType: vtblAddr == null");
      }

      return false;
    }

    // The first implementation searched three locations for this vtbl
    // value; scanning through the entire object was considered, but
    // we thought we knew where we were looking, and looking only in
    // these specific locations should reduce the probability of
    // mistaking random bits as a pointer (although, realistically
    // speaking, the likelihood of finding a match between the bit
    // pattern of, for example, a double and the vtbl is vanishingly
    // small.)
    //    1. The first word of the object (should handle MSVC++ as
    //    well as the SparcWorks compilers with compatibility set to
    //    v5.0 or greater)
    //    2. and 3. The last two Address-aligned words of the part of
    //    the object defined by its topmost polymorphic superclass.
    //    This should handle the SparcWorks compilers, v4.2 or
    //    earlier, as well as any other compilers which place the vptr
    //    at the end of the user-defined fields of the first base
    //    class with virtual functions.
    //
    // Unfortunately this algorithm did not work properly for the
    // specific case of the ThreadShadow/Thread inheritance situation,
    // because the Solaris compiler seems to cleverly eliminate the
    // vtbl for ThreadShadow since the only virtual is empty. (We
    // should get rid of the ThreadShadow and fix the include
    // databases, but need to postpone this for the present.) The
    // current solution performs the three-location check for this
    // class and all of its known superclasses rather than just the
    // topmost polymorphic one.

    Type curType = type;

    try {
      while (curType != null) {
        // Using the size information we have for this type, check the
        // three locations described above.

        // (1)
        if (vtblAddr.equals(addr.getAddressAt(0))) {
          return true;
        }

        // (2)
        long offset = curType.getSize();
        // I don't think this should be misaligned under any
        // circumstances, but I'm not sure (FIXME: also not sure which
        // way to go here, up or down -- assuming down)
        offset -= (offset % getAddressSize());
        if (offset <= 0) {
          return false;
        }
        if (vtblAddr.equals(addr.getAddressAt(offset))) {
          return true;
        }
        offset -= getAddressSize();
        if (offset <= 0) {
          return false;
        }
        if (vtblAddr.equals(addr.getAddressAt(offset))) {
          return true;
        }

        curType = curType.getSuperclass();
      }
    }
    catch (Exception e) {
      // Any UnmappedAddressExceptions, etc. are a good indication
      // that the pointer is not of the specified type
      if (DEBUG) {
        System.err.println("BasicTypeDataBase.addressTypeIsEqualToType: exception occurred during lookup:");
        e.printStackTrace();
      }

      return false;
    }

    if (DEBUG) {
      System.err.println("BasicTypeDataBase.addressTypeIsEqualToType: all vptr tests failed for type " +
                         type.getName());
    }

    return false;
  }

  public Type findDynamicTypeForAddress(Address addr, Type baseType) {
    // This implementation should be suitably platform-independent; we
    // search nearby memory for the vtbl value of the given type.

    if (vtblForType(baseType) == null) {
      // Type was not polymorphic which is an error of some sort
      throw new InternalError(baseType + " does not appear to be polymorphic");
    }

    // This is a more restricted version of guessTypeForAddress since
    // that function has some limitations since it doesn't really know
    // where in the hierarchy a virtual type starts and just poking
    // around in memory is likely to trip over some vtable address,
    // resulting in false positives.  Eventually all uses should
    // switch to this logic but in the interests of stability it will
    // be separate for the moment.

    // Assuming that the base type is truly the first polymorphic type
    // then the vtbl for all subclasss should be at several defined
    // locations so only those locations will be checked.  It's also
    // required that the caller knows that the static type is at least
    // baseType.  See the notes in guessTypeForAddress for the logic of
    // the locations searched.

    Address loc1 = addr.getAddressAt(0);
    Address loc2 = null;
    Address loc3 = null;
    long offset2 = baseType.getSize();
    // I don't think this should be misaligned under any
    // circumstances, but I'm not sure (FIXME: also not sure which
    // way to go here, up or down -- assuming down)
    offset2 = offset2 - (offset2 % getAddressSize()) - getAddressSize();
    if (offset2 > 0) {
      loc2 = addr.getAddressAt(offset2);
    }
    long offset3 = offset2 - getAddressSize();
    if (offset3 > 0) {
      loc3 = addr.getAddressAt(offset3);
    }

    Type loc2Match = null;
    Type loc3Match = null;
    for (Iterator iter = getTypes(); iter.hasNext(); ) {
      Type type = (Type) iter.next();
      Type superClass = type;
      while (superClass != baseType && superClass != null) {
        superClass = superClass.getSuperclass();
      }
      if (superClass == null) continue;
      Address vtblAddr = vtblForType(type);
      if (vtblAddr == null) {
        // This occurs sometimes for intermediate types that are never
        // instantiated.
        if (DEBUG) {
          System.err.println("null vtbl for " + type);
        }
        continue;
      }
      // Prefer loc1 match
      if (vtblAddr.equals(loc1)) return type;
      if (loc2 != null && loc2Match == null && vtblAddr.equals(loc2)) {
          loc2Match = type;
      }
      if (loc3 != null && loc3Match == null && vtblAddr.equals(loc3)) {
          loc3Match = type;
      }
    }
    if (loc2Match != null) return loc2Match;
    if (loc3Match != null) return loc3Match;
    return null;
  }

  public Type guessTypeForAddress(Address addr) {
    for (Iterator iter = getTypes(); iter.hasNext(); ) {
      Type t = (Type) iter.next();
      if (addressTypeIsEqualToType(addr, t)) {
        return t;
      }
    }
    return null;
  }

  public long cIntegerTypeMaxValue(long sizeInBytes, boolean isUnsigned) {
    return machDesc.cIntegerTypeMaxValue(sizeInBytes, isUnsigned);
  }

  public long cIntegerTypeMinValue(long sizeInBytes, boolean isUnsigned) {
    return machDesc.cIntegerTypeMinValue(sizeInBytes, isUnsigned);
  }

  public Iterator getTypes() {
    return nameToTypeMap.values().iterator();
  }

  public Iterator getIntConstants() {
    return nameToIntConstantMap.keySet().iterator();
  }

  public Iterator getLongConstants() {
    return nameToLongConstantMap.keySet().iterator();
  }

  //--------------------------------------------------------------------------------
  // Public routines only for use by the database builder
  //

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJBooleanType(Type type) {
    jbooleanType = type;
  }

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJByteType(Type type) {
    jbyteType = type;
  }

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJCharType(Type type) {
    jcharType = type;
  }

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJDoubleType(Type type) {
    jdoubleType = type;
  }

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJFloatType(Type type) {
    jfloatType = type;
  }

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJIntType(Type type) {
    jintType = type;
  }

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJLongType(Type type) {
    jlongType = type;
  }

  /** This method should only be called by the builder of the
      TypeDataBase and at most once */
  public void setJShortType(Type type) {
    jshortType = type;
  }

  /** This method should only be used by the builder of the
      TypeDataBase. Throws a RuntimeException if a class with this
      name was already present. */
  public void addType(Type type) {
    if (nameToTypeMap.get(type.getName()) != null) {
      throw new RuntimeException("type of name \"" + type.getName() + "\" already present");
    }

    nameToTypeMap.put(type.getName(), type);
  }

  /** This method should only be used by the builder of the
      TypeDataBase. Throws a RuntimeException if this class was not
      present. */
  public void removeType(Type type) {
    Type curType = (Type) nameToTypeMap.get(type.getName());
    if (curType == null) {
      throw new RuntimeException("type of name \"" + type.getName() + "\" not present");
    }

    if (!curType.equals(type)) {
      throw new RuntimeException("a different type of name \"" + type.getName() + "\" was present");
    }

    nameToTypeMap.remove(type.getName());
  }

  /** This method should only be used by the builder of the
      TypeDataBase. Throws a RuntimeException if an integer constant
      with this name was already present. */
  public void addIntConstant(String name, int value) {
    if (nameToIntConstantMap.get(name) != null) {
      throw new RuntimeException("int constant of name \"" + name + "\" already present");
    }

    nameToIntConstantMap.put(name, new Integer(value));
  }

  /** This method should only be used by the builder of the
      TypeDataBase. Throws a RuntimeException if an integer constant
      with this name was not present. */
  public void removeIntConstant(String name) {
    Integer curConstant = (Integer) nameToIntConstantMap.get(name);
    if (curConstant == null) {
      throw new RuntimeException("int constant of name \"" + name + "\" not present");
    }

    nameToIntConstantMap.remove(name);
  }

  /** This method should only be used by the builder of the
      TypeDataBase. Throws a RuntimeException if a long constant with
      this name was already present. */
  public void addLongConstant(String name, long value) {
    if (nameToLongConstantMap.get(name) != null) {
      throw new RuntimeException("long constant of name \"" + name + "\" already present");
    }

    nameToLongConstantMap.put(name, new Long(value));
  }

  /** This method should only be used by the builder of the
      TypeDataBase. Throws a RuntimeException if a long constant with
      this name was not present. */
  public void removeLongConstant(String name) {
    Long curConstant = (Long) nameToLongConstantMap.get(name);
    if (curConstant == null) {
      throw new RuntimeException("long constant of name \"" + name + "\" not present");
    }

    nameToLongConstantMap.remove(name);
  }
}
