/*
 * Copyright (C) 2014, United States Government, as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All rights reserved.
 *
 * The Java Pathfinder core (jpf-core) platform is 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 gov.nasa.jpf.vm;

import gov.nasa.jpf.JPFException;

import java.lang.reflect.Method;
import java.util.ArrayList;


/**
 * various type mangling/demangling routines
 *
 * This reflects the general type id mess in Java. We support the following:
 *
 *  builtin type: byte - T_BOOLEAN and the like
 *  type name: String - according to JLS 6.7 ("int", "x.Y[]")
 *  type signature: String - like JNI ("I", "[Lx/Y;")
 *  type classname: String - e.g. "int", "[I", "x.Y", "[Lx.Y;"
 */
public class Types {

  // these have the same values as the BCEL Constants since we don't want to break compiled code
  public static final byte T_NONE      = 0; // illegal type
  
  public static final byte T_BOOLEAN   = 4;
  public static final byte T_BYTE      = 8;
  public static final byte T_CHAR      = 5;
  public static final byte T_SHORT     = 9;
  public static final byte T_INT       = 10;
  public static final byte T_LONG      = 11;
  public static final byte T_FLOAT     = 6;
  public static final byte T_DOUBLE    = 7;
  public static final byte T_REFERENCE = 14;
  public static final byte T_ARRAY     = 13;  // <2do> do we need this in addition to T_REFERENCE?
  public static final byte T_VOID      = 12;

  
  public static byte[] getArgumentTypes (String signature) {
    int i;
    int j;
    int nArgs;

    for (i = 1, nArgs = 0; signature.charAt(i) != ')'; nArgs++) {
      i += getTypeLength(signature, i);
    }

    byte[] args = new byte[nArgs];

    for (i = 1, j = 0; j < nArgs; j++) {
      int    end = i + getTypeLength(signature, i);
      String arg = signature.substring(i, end);
      i = end;

      args[j] = getBuiltinTypeFromSignature(arg);
    }

    return args;
  }

  public static String[] getArgumentTypeNames (String signature) {
    int len = signature.length();

    if ((len > 1) && (signature.charAt(1) == ')')) {
      return new String[0]; // 'no args' shortcut
    }
    
    ArrayList<String> a = new ArrayList<String>();

    for (int i = 1; signature.charAt(i) != ')';) {
      int end = i + getTypeLength(signature,i);
      String arg = signature.substring(i, end);
      i = end;

      a.add(getTypeName(arg));
    }

    String[] typeNames = new String[a.size()];
    a.toArray(typeNames);
    
    return typeNames;
  }
  
  public static String dequalify (String typeName){
    int idx = typeName.lastIndexOf('.');
    if (idx > 0) {
      return typeName.substring(idx + 1);
    } else {
      return typeName;
    }    
  }
  
  public static String getDequalifiedMethodSignature (String mName){
    int idx = mName.indexOf('(');
    String sig = mName.substring(idx);
    
    return mName.substring(0, idx) + getDequalifiedArgumentSignature(sig);
  }
  
  public static String getDequalifiedArgumentSignature (String sig){
    String[] argTypes = getArgumentTypeNames(sig);
    StringBuilder sb = new StringBuilder();
    sb.append('(');
    for (int i=0; i<argTypes.length; i++){
      if (i>0){
        sb.append(',');
      }
      sb.append(dequalify(argTypes[i]));
    }
    sb.append(')');
    return sb.toString();
  }
  
  public static String getDequalifiedTypeName (String sig){
    String tn = getTypeName(sig);
    return dequalify(tn);
  }
  
  public static String getArgumentSignature (String[] typeNames, boolean qualified){
    StringBuilder sb = new StringBuilder();
    sb.append('(');
    for (int i=0; i<typeNames.length; i++){
      if (i>0){
        sb.append(',');
      }
      
      String tn = getTypeName(typeNames[i]);
      if (!qualified){
        int idx = tn.lastIndexOf('.');
        if (idx >0){
          tn = tn.substring(idx+1);
        }
      }
      
      sb.append( tn);
    }
    sb.append(')');
    return sb.toString();
  }
  
  /**
   * get size in stack slots (ints), excluding this
   */
  public static int getArgumentsSize (String sig) {
    int  n = 0;
    for (int i = 1; sig.charAt(i) != ')'; i++) {
      switch (sig.charAt(i)) {
      case 'L':
        do i++; while (sig.charAt(i) != ';');
        n++;
        break;
      case '[':
        do i++; while (sig.charAt(i) == '[');
        if (sig.charAt(i) == 'L') {
          do i++; while (sig.charAt(i) != ';');
        }
        n++;
        break;
      case 'J':
      case 'D':
        // the two-slot types
        n += 2;
        break;
      default:
        // just one slot entry
        n++;
      }
    }
    return n;
  }

  public static String getArrayElementType (String type) {
    if (type.charAt(0) != '[') {
      throw new JPFException("not an array type: " + type);
    }

    return type.substring(1);
  }

  public static String getComponentTerminal (String type) {
    if (type.charAt(0) != '[') {
      throw new JPFException("not an array type: " + type);
    }

    if(isReferenceSignature(type)) {
      return type.substring(type.indexOf('L') + 1 , type.indexOf(';'));
    } else {
      return type.substring(type.lastIndexOf('[') + 1);
    }
  }

  public static byte getBuiltinTypeFromSignature (String signature) {
    switch (signature.charAt(0)) {
    case 'B':
      return T_BYTE;

    case 'C':
      return T_CHAR;

    case 'D':
      return T_DOUBLE;

    case 'F':
      return T_FLOAT;

    case 'I':
      return T_INT;

    case 'J':
      return T_LONG;

    case 'L':
      return T_REFERENCE;

    case 'S':
      return T_SHORT;

    case 'V':
      return T_VOID;

    case 'Z':
      return T_BOOLEAN;

    case '[':
      return T_ARRAY;
    }

    throw new JPFException("invalid type string: " + signature);
  }

  /**
   * get the argument type part of the signature out of a
   * JNI mangled method name.
   * Note this is not the complete signature, since we don't have a
   * return type (which is superfluous since it's not overloading,
   * but unfortunately part of the signature in the class file)
   */
  public static String getJNISignature (String mangledName) {
    int    i = mangledName.indexOf("__");
    String sig = null;

    if (i > 0) {
      int k = 0;      
      int r = mangledName.indexOf("__", i+2); // maybe there is a return type part
      boolean gotReturnType = false;
      int len = mangledName.length();
      char[] buf = new char[len + 2];

      buf[k++] = '(';

      for (i += 2; i < len; i++) {

        if (i == r) { // here comes the return type part (that's not JNI, only MJI
          if ((i + 2) < len) {
            i++;
            buf[k++] = ')';
            gotReturnType = true;
            continue;
          } else {
            break;
          }
        }
        
        char c = mangledName.charAt(i);
        if (c == '_') {
          i++;

          if (i < len) {
            c = mangledName.charAt(i);

            switch (c) {
            case '1':
              buf[k++] = '_';

              break;

            case '2':
              buf[k++] = ';';

              break;

            case '3':
              buf[k++] = '[';

              break;

            default:
              buf[k++] = '/';
              buf[k++] = c;
            }
          } else {
            buf[k++] = '/';
          }
        } else {
          buf[k++] = c;
        }
      }

      if (!gotReturnType) {
        // if there was no return type spec, assume 'void'
        buf[k++] = ')';
        buf[k++] = 'V';
      }
        
      sig = new String(buf, 0, k);
    }

    // Hmm, maybe we should return "()V" instead of null, but that seems a bit too assuming
    return sig;
  }

  public static String getJNIMangledMethodName (Method m) {
    String      name = m.getName();
    Class<?>[]    pt = m.getParameterTypes();
    StringBuilder  s = new StringBuilder(name.length() + (pt.length * 16));

    s.append(name);
    s.append("__");

    // <2do> not very efficient, but we don't care for now
    for (int i = 0; i < pt.length; i++) {
      s.append(getJNITypeCode(pt[i].getName()));
    }

    // the return type part, which is not in JNI, but is required for
    // handling covariant return types
    Class<?> rt = m.getReturnType();
    s.append("__");
    s.append(getJNITypeCode(rt.getName()));
    
    return s.toString();
  }

  public static String getJNIMangledMethodName (String cls, String name,
                                                String signature) {
    StringBuilder s = new StringBuilder(signature.length() + 10);
    int           i;
    char          c;
    int           slen = signature.length();
    
    if (cls != null) {
      s.append(cls.replace('.', '_'));
    }

    s.append(name);
    s.append("__");

    // as defined in the JNI specs
    for (i = 1; i<slen; i++) {
      c = signature.charAt(i);
      switch (c) {
      case '/':
        s.append('_');
        break;

      case '_':
        s.append("_1");
        break;

      case ';':
        s.append("_2");
        break;

      case '[':
        s.append("_3");
        break;

      case ')':
        // the return type part - note this is not JNI, but saves us a lot of trouble with
        // the covariant return types of Java 1.5        
        s.append("__");
        break;
        
      default:
        s.append(c);
      }
    }

    return s.toString();
  }

  /**
   * return the name part of a JNI mangled method name (which is of
   * course not completely safe - you should only use it if you know
   * this is a JNI name)
   */
  public static String getJNIMethodName (String mangledName) {
    // note that's the first '__' group, which marks the beginning of the arg types
    int i = mangledName.indexOf("__");

    if (i > 0) {
      return mangledName.substring(0, i);
    } else {
      return mangledName;
    }
  }

  /**
   * type is supposed to be Class.getName conforming, i.e.
   * 
   * int    -> int
   * int[]  -> [I
   * String -> java.lang.String
   * String[] -> [Ljava.lang.String;
   * String[][] -> [[Ljava.lang.String;
   * 
   * <2do> this is really not very efficient
   */
  public static String getJNITypeCode (String type) {
    StringBuilder sb = new StringBuilder(32);
    int l = type.length() - 1;
    int i;

    // Class.getName arrays "[...type"
    for ( i=0; type.charAt(i) == '['; i++){
      sb.append("_3");
    }
    
    // conventional arrays "type[]..."
    for (; type.charAt(l) == ']'; l -= 2) {
      sb.append("_3");
    }

    type = type.substring(i, l + 1);

    if (type.equals("int") || type.equals("I")) {
      sb.append('I');
    } else if (type.equals("long") || type.equals("J")) {
      sb.append('J');
    } else if (type.equals("boolean") || type.equals("Z")) {
      sb.append('Z');
    } else if (type.equals("char") || type.equals("C")) {
      sb.append('C');
    } else if (type.equals("byte")  || type.equals("B")) {
      sb.append('B');
    } else if (type.equals("short") || type.equals("S")) {
      sb.append('S');
    } else if (type.equals("double") || type.equals("D")) {
      sb.append('D');
    } else if (type.equals("float") || type.equals("F")) {
      sb.append('F');
    } else if (type.equals("void") || type.equals("V")) {  // for return types
      sb.append('V');
    } else { // reference type
      if (type.charAt(0) != 'L'){
        sb.append('L');
      }

      l = type.length();
      for (i=0; i < l; i++) {
        char c = type.charAt(i);

        switch (c) {
        case '.':
          sb.append('_');
          break;

        case '_':
          sb.append("_1");
          break;
          
        case ';':
          break;
          
        default:
          sb.append(c);
        }
      }

      sb.append("_2");
      
    }

    return sb.toString();
  }

  // this includes the return type part
  public static int getNumberOfStackSlots (String signature, boolean isStatic) {
    int nArgSlots = 0;
    int n = isStatic ? 0 : 1;
    int sigLen = signature.length();

    for (int i = 1; i < sigLen; i++) {
      switch (signature.charAt(i)) {
      case ')' : // end of arg part, but there still might be a return type
        nArgSlots = n;
        n = 0;
        break;
      case 'L':   // reference = 1 slot
        i = signature.indexOf(';', i);
        n++;
        break;
      case '[':
        do i++; while (signature.charAt(i) == '[');
        if (signature.charAt(i) == 'L') {
          i = signature.indexOf(';', i);
        }
        n++;
        break;
      case 'J':
      case 'D':
        n+=2;
        break;
      default:
        n++;
      }
    }
    
    return Math.max(n, nArgSlots);
  }
  
  public static int getNumberOfArguments (String signature) {
    int  i,n;
    int sigLen = signature.length();

    for (i = 1, n = 0; i<sigLen; n++) {
      switch (signature.charAt(i)) {
      case ')' :
        return n;
      case 'L':
        do i++; while (signature.charAt(i) != ';');
        break;

      case '[':
        do i++; while (signature.charAt(i) == '[');
        if (signature.charAt(i) == 'L') {
          do i++; while (signature.charAt(i) != ';');
        }
        break;

      default:
        // just a single type char
      }

      i++;
    }

    assert (false) : "malformed signature: " + signature;
    return n; // that would be a malformed signature
  }

  public static boolean isReferenceSignature(String signature){
    return signature.charAt(signature.length()-1) == ';';
  }

  public static boolean isReference (String type) {
    int t = getBuiltinTypeFromSignature(type);

    return (t == T_ARRAY) || (t == T_REFERENCE);
  }

  public static boolean isArray (String type) {
    int t = getBuiltinTypeFromSignature(type);

    return (t == T_ARRAY);
  }

  public static byte getReturnBuiltinType (String signature) {
    int i = signature.indexOf(')');

    return getBuiltinTypeFromSignature(signature.substring(i + 1));
  }

  public static String getReturnTypeSignature(String signature){
    int i = signature.indexOf(')');
    return signature.substring(i + 1);
  }

  public static String getReturnTypeName (String signature){
    int i = signature.indexOf(')');
    return getTypeName(signature.substring(i+1));
  }
  
  public static String getTypeSignature (String type, boolean asDotNotation) {
    String  t = null;
    int arrayDim = 0;
    
    type = asDotNotation ? type.replace('/', '.') : type.replace('.', '/');
    
    if ((type.charAt(0) == '[') || (type.endsWith(";"))) {  // [[[L...;
      t = type;
      
    } else {
      
      while (type.endsWith("[]")) { // type[][][]
        type = type.substring(0, type.length() - 2);
        arrayDim++;
      }
      
      if (type.equals("byte")) {
        t = "B";
      } else if (type.equals("char")) {
        t = "C";
      } else if (type.equals("short")) {
        t = "S";
      } else if (type.equals("int")) {
        t = "I";
      } else if (type.equals("float")) {
        t = "F";
      } else if (type.equals("long")) {
        t = "J";
      } else if (type.equals("double")) {
        t = "D";
      } else if (type.equals("boolean")) {
        t = "Z";
      } else if (type.equals("void")) {
        t = "V";
      } else if (type.endsWith(";")) {
        t = type;
        
      } else {
        t = "L" + type + ';';
      }
      
      while (arrayDim-- > 0) {
        t = "[" + t;
      }
    }

    return t;
  }

  public static byte getBuiltinType(String typeName){
      if (typeName.equals("byte")) {
        return T_BYTE;
      } else if (typeName.equals("char")) {
        return T_CHAR;
      } else if (typeName.equals("short")) {
        return T_SHORT;
      } else if (typeName.equals("int")) {
        return T_INT;
      } else if (typeName.equals("float")) {
        return T_FLOAT;
      } else if (typeName.equals("long")) {
        return T_LONG;
      } else if (typeName.equals("double")) {
        return T_DOUBLE;
      } else if (typeName.equals("boolean")) {
        return T_BOOLEAN;
      } else if (typeName.equals("void")) {
        return T_VOID;
      } else {
        if (typeName.charAt(typeName.length()-1) == ']'){
          return T_ARRAY;
        } else {
          return T_REFERENCE;
        }
      }
  }

  public static String getBoxedType (byte type) {
	  switch (type) {
	  case Types.T_BOOLEAN:
		  return "Boolean";
	  case Types.T_BYTE:
		  return "Byte";
	  case Types.T_CHAR:
		  return "Character";
	  case Types.T_SHORT:
		  return "Short";
	  case Types.T_INT:
		  return "Integer";
	  case Types.T_LONG:
		  return "Long";
	  case Types.T_FLOAT:
		  return "Float";
	  case Types.T_DOUBLE:
		  return "Double";
	  default:
		  return null;
	  }
  }
  
  public static byte getUnboxedType (String typeName){
    if (typeName.startsWith("java.lang.")){
      typeName = typeName.substring(10);
      if (typeName.equals("Boolean")){
        return T_BOOLEAN;
      } else if (typeName.equals("Byte")){
        return T_BYTE;
      } else if (typeName.equals("Character")){
        return T_CHAR;
      } else if (typeName.equals("Short")){
        return T_SHORT;
      } else if (typeName.equals("Integer")){
        return T_INT;
      } else if (typeName.equals("Long")){
        return T_LONG;
      } else if (typeName.equals("Float")){
        return T_FLOAT;
      } else if (typeName.equals("Double")){
        return T_DOUBLE;
      }
    }
    
    // everything else can't be a box type
    if (typeName.charAt(0) == '[' || typeName.charAt(typeName.length()-1) == ']'){
      return T_ARRAY;
    } else {
      return T_REFERENCE;
    }
  }
  
  public static String getClassNameFromSignature (String signature){
    if (signature.charAt(signature.length()-1) == ';'){ // reference
      return signature.replace('/', '.');

    } else { // builtin
      switch (signature.charAt(0)){
        case 'Z': return "boolean";
        case 'B': return "byte";
        case 'C': return "char";
        case 'S': return "short";
        case 'I': return "int";
        case 'J': return "long";
        case 'F': return "float";
        case 'D': return "double";
        default:
          throw new JPFException("illegal type signature: " + signature);
      }
    }
  }

  /**
   * get the canonical representation of a type name, which happens to be
   *  (1) the name of the builtin type (e.g. "int")
   *  (2) the normal dot name for ordinary classes (e.g. "java.lang.String")
   *  (3) the coded dot name for arrays (e.g. "[B", "[[C", or "[Ljava.lang.String;")
   *  
   * not sure if we need to support internal class names (which use '/'
   * instead of '.' as separators
   *  
   * no idea what's the logic behind this, but let's implement it
   */
  public static String getClassNameFromTypeName (String typeName) {
    typeName = typeName.replace('/','.');
    int n = typeName.length()-1;
    
    if (typeName.charAt(0) == '['){ // the "[<type>" notation
      if (typeName.charAt(1) == 'L'){
        if (typeName.charAt(n) != ';'){
          typeName = typeName + ';';
        }
      }
      
      return typeName;
    }
    
    int i=typeName.indexOf("[]");
    if (i>0){ // the sort of "<type>[]"
      StringBuilder sb = new StringBuilder();
      sb.append('[');
      for (int j=i; (j=typeName.indexOf('[',j+1)) >0;){
        sb.append('[');
      }
      
      typeName = typeName.substring(0,i);
      if (isBasicType(typeName)){
        sb.append( getTypeSignature(typeName, true));
      } else {
        sb.append('L');
        sb.append(typeName);
        sb.append(';');
      }
      
      return sb.toString();
    }
    
    if (typeName.charAt(n) == ';') {
      return typeName.substring(1,n);
    }
    
    return typeName;
  }

  
  public static boolean isTypeCode (String t) {
    char c = t.charAt(0);

    if (c == '[') {
      return true;
    }

    if ((t.length() == 1) &&
            ((c == 'B') || (c == 'I') || (c == 'S') || (c == 'C') ||
              (c == 'F') || (c == 'J') || (c == 'D') || (c == 'Z'))) {
      return true;
    }

    if (t.endsWith(";")) {
      return true;
    }

    return false;
  }

  public static boolean isBasicType (String typeName){
    return ("boolean".equals(typeName) ||
        "byte".equals(typeName) ||
        "char".equals(typeName) ||
        "int".equals(typeName) ||
        "long".equals(typeName) ||
        "double".equals(typeName) ||
        "short".equals(typeName) ||
        "float".equals(typeName));
  }

  public static byte getTypeCode (String signature){
    char c = signature.charAt(0);

    switch (c) {
      case 'B':
        return T_BYTE;

      case 'C':
        return T_CHAR;

      case 'D':
        return T_DOUBLE;

      case 'F':
        return T_FLOAT;

      case 'I':
        return T_INT;

      case 'J':
        return T_LONG;

      case 'L':
        return T_REFERENCE;

      case 'S':
        return T_SHORT;

      case 'V':
        return T_VOID;

      case 'Z':
        return T_BOOLEAN;

      case '[':
        return T_ARRAY;

      default:
        throw new JPFException("unknow typecode: " + signature);
    }
  }
  
  /**
   * return the qualified signature name according to JLS 6.7 (e.g. "int", "x.Y[]")
   */
  public static String getTypeName (String signature) {
    int  len = signature.length();
    char c = signature.charAt(0);

    if (len == 1) {
      switch (c) {
      case 'B':
        return "byte";

      case 'C':
        return "char";

      case 'D':
        return "double";

      case 'F':
        return "float";

      case 'I':
        return "int";

      case 'J':
        return "long";

      case 'S':
        return "short";

      case 'V':
        return "void";

      case 'Z':
        return "boolean";
      }
    }

    if (c == '[') {
      return getTypeName(signature.substring(1)) + "[]";
    }

    int len1 = len-1;
    if (signature.charAt(len1) == ';') {
      return signature.substring(1, len1).replace('/', '.');
    }

    throw new JPFException("invalid type string: " + signature);
  }

  /** thoses are according to the arrayType codes of the newarray JVMS definition */
  public static String getElementDescriptorOfType (int arrayType){
    switch (arrayType){
      case 4: return "Z";
      case 5: return "C";
      case 6: return "F";
      case 7: return "D";
      case 8: return "B";
      case 9: return "S";
      case 10: return "I";
      case 11: return "J";
    }
    return null;
  }

  /**
   * what would be the info size in bytes, not words
   * (we ignore 64bit machines for now)
   */
  public static int getTypeSizeInBytes (String signature) {
    switch (signature.charAt(0)) {
      case 'V':
        return 0;
        
      case 'Z': // that's a stretch, but we assume boolean uses the smallest addressable size
      case 'B':
        return 1;
        
      case 'S':
      case 'C':
        return 2;
        
      case 'L':
      case '[':
      case 'F':
      case 'I':
        return 4;
        
      case 'D':
      case 'J':
        return 8;
    }

    throw new JPFException("invalid type string: " + signature);
  }
  
  public static int getTypeSize (String signature) {
    switch (signature.charAt(0)) {
    case 'V':
      return 0;

    case 'B':
    case 'C':
    case 'F':
    case 'I':
    case 'L':
    case 'S':
    case 'Z':
    case '[':
      return 1;

    case 'D':
    case 'J':
      return 2;
    }

    throw new JPFException("invalid type string: " + signature);
  }

  public static int getTypeSize (byte typeCategory){
    if (typeCategory == T_LONG || typeCategory == T_DOUBLE){
      return 2;
    } else {
      return 1;
    }
  }

  /**
   * what would be the info size in bits
   */
  public static int getTypeSizeInBits (byte typeCategory) {
    switch (typeCategory) {
      case T_LONG:
      case T_DOUBLE:
        return 64;
      case Types.T_BOOLEAN:
        return 1;
      case Types.T_ARRAY:
      case Types.T_REFERENCE:
      case Types.T_INT:
      case Types.T_FLOAT:
        return 32;
      case Types.T_SHORT:
      case Types.T_CHAR:
        return 16;
      case Types.T_BYTE:
        return 8;
    }

    throw new JPFException("invalid type type category id: " + typeCategory);
  }

  public static int getTypeSizeInBits (String signature) {
    switch (signature.charAt(0)) {
      case 'V':
        return 0;

      case 'Z':
        return 1;
      case 'B':
        return 8;

      case 'S':
      case 'C':
        return 16;

      case 'L':
      case '[':
      case 'F':
      case 'I':
        return 32;

      case 'D':
      case 'J':
        return 64;
    }

    throw new JPFException("invalid type string: " + signature);
  }
  
  public static String asTypeName (String type) {
    if (type.startsWith("[") || type.endsWith(";")) {
      return getTypeName(type);
    }

    return type;
  }

  public static int booleanToInt (boolean b) {
    return b ? 1 : 0;
  }

  public static long doubleToLong (double d) {
    return Double.doubleToLongBits(d);
  }

  public static int floatToInt (float f) {
    return Float.floatToIntBits(f);
  }

  public static int hiDouble (double d) {
    return hiLong(Double.doubleToLongBits(d));
  }

  public static int hiLong (long l) {
    return (int) (l >> 32);
  }

  public static boolean instanceOf (ClassInfo ci, String ofTypeSignature) {
    int bOfType = getBuiltinTypeFromSignature(ofTypeSignature);
    if (ci.isArray() && bOfType == T_ARRAY) {
      return instanceOf(ci.getComponentClassInfo(), getArrayElementType(ofTypeSignature));
    }

    if (ci.isPrimitive()) {
      int bType = getBuiltinTypeFromSignature(ci.getSignature());
      return bType == bOfType;
    }

    return ci.isInstanceOf(getTypeName(ofTypeSignature));
  }

  public static boolean intToBoolean (int i) {
    return i != 0;
  }

  public static float intToFloat (int i) {
    return Float.intBitsToFloat(i);
  }

  public static double intsToDouble (int l, int h) {
    long bits = ((long) h << 32) | (/*(long)*/ l & 0xFFFFFFFFL);
    return Double.longBitsToDouble(bits);
  }

  public static long intsToLong (int l, int h) {
    return ((long) h << 32) | (/*(long)*/ l & 0xFFFFFFFFL);
  }

  public static int loDouble (double d) {
    return loLong(Double.doubleToLongBits(d));
  }

  public static int loLong (long l) {
    return (int) (l & 0xFFFFFFFFL);
  }

  public static double longToDouble (long l) {
    return Double.longBitsToDouble(l);
  }

  private static int getTypeLength (String signature, int idx) {
    switch (signature.charAt(idx)) {
    case 'B':
    case 'C':
    case 'D':
    case 'F':
    case 'I':
    case 'J':
    case 'S':
    case 'V':
    case 'Z':
      return 1;

    case '[':
      return 1 + getTypeLength(signature, idx + 1);

    case 'L':

      int semicolon = signature.indexOf(';', idx);

      if (semicolon == -1) {
        throw new JPFException("invalid type signature: " +
                                         signature);
      }

      return semicolon - idx + 1;
    }

    throw new JPFException("invalid type signature");
  }

  /**
   * return the JPF internal representation of a method signature that is given
   * in dot-notation (like javap),
   *
   *  e.g.  "int foo(int[],java.lang.String)" -> "foo([ILjava/lang/String;)I"
   *
   */
  public static String getSignatureName (String methodDecl) {

    StringBuffer sb = new StringBuffer(128);
    String retType = null;

    int i = methodDecl.indexOf('(');
    if (i>0){

      //--- name and return type
      String[] a = methodDecl.substring(0, i).split(" ");
      if (a.length > 0){
        sb.append(a[a.length-1]);

        if (a.length > 1){
          retType = getTypeSignature(a[a.length-2], false);
        }
      }

      //--- argument types
      int j = methodDecl.lastIndexOf(')');
      if (j > 0){
        sb.append('(');
        for (String type : methodDecl.substring(i+1,j).split(",")){
          if (!type.isEmpty()){
            type = type.trim();
            if (!type.isEmpty()){
              sb.append( getTypeSignature(type,false));
            }
          }
        }
        sb.append(')');

        if (retType != null){
          sb.append(retType);
        }

        return sb.toString();
      }
    }

    throw new JPFException("invalid method declaration: " + methodDecl);
  }
  
}
