/** Type-munging for field setting and method invocation. Conforms to LC3 specification */
  static Object coerceTypeImpl(Class<?> type, Object value) {
    if (value != null && value.getClass() == type) {
      return value;
    }

    switch (getJSTypeCode(value)) {
      case JSTYPE_NULL:
        // raise error if type.isPrimitive()
        if (type.isPrimitive()) {
          reportConversionError(value, type);
        }
        return null;

      case JSTYPE_UNDEFINED:
        if (type == ScriptRuntime.StringClass || type == ScriptRuntime.ObjectClass) {
          return "undefined";
        } else {
          reportConversionError("undefined", type);
        }
        break;

      case JSTYPE_BOOLEAN:
        // Under LC3, only JS Booleans can be coerced into a Boolean value
        if (type == Boolean.TYPE
            || type == ScriptRuntime.BooleanClass
            || type == ScriptRuntime.ObjectClass) {
          return value;
        } else if (type == ScriptRuntime.StringClass) {
          return value.toString();
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_NUMBER:
        if (type == ScriptRuntime.StringClass) {
          return ScriptRuntime.toString(value);
        } else if (type == ScriptRuntime.ObjectClass) {
          return coerceToNumber(Double.TYPE, value);
        } else if ((type.isPrimitive() && type != Boolean.TYPE)
            || ScriptRuntime.NumberClass.isAssignableFrom(type)) {
          return coerceToNumber(type, value);
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_STRING:
        if (type == ScriptRuntime.StringClass || type.isInstance(value)) {
          return value;
        } else if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) {
          // Special case for converting a single char string to a
          // character
          // Placed here because it applies *only* to JS strings,
          // not other JS objects converted to strings
          if (((String) value).length() == 1) {
            return new Character(((String) value).charAt(0));
          } else {
            return coerceToNumber(type, value);
          }
        } else if ((type.isPrimitive() && type != Boolean.TYPE)
            || ScriptRuntime.NumberClass.isAssignableFrom(type)) {
          return coerceToNumber(type, value);
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_JAVA_CLASS:
        if (value instanceof Wrapper) {
          value = ((Wrapper) value).unwrap();
        }

        if (type == ScriptRuntime.ClassClass || type == ScriptRuntime.ObjectClass) {
          return value;
        } else if (type == ScriptRuntime.StringClass) {
          return value.toString();
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_JAVA_OBJECT:
      case JSTYPE_JAVA_ARRAY:
        if (value instanceof Wrapper) {
          value = ((Wrapper) value).unwrap();
        }
        if (type.isPrimitive()) {
          if (type == Boolean.TYPE) {
            reportConversionError(value, type);
          }
          return coerceToNumber(type, value);
        } else {
          if (type == ScriptRuntime.StringClass) {
            return value.toString();
          } else {
            if (type.isInstance(value)) {
              return value;
            } else {
              reportConversionError(value, type);
            }
          }
        }
        break;

      case JSTYPE_OBJECT:
        if (type == ScriptRuntime.StringClass) {
          return ScriptRuntime.toString(value);
        } else if (type.isPrimitive()) {
          if (type == Boolean.TYPE) {
            reportConversionError(value, type);
          }
          return coerceToNumber(type, value);
        } else if (type.isInstance(value)) {
          return value;
        } else if (type == ScriptRuntime.DateClass && value instanceof NativeDate) {
          double time = ((NativeDate) value).getJSTimeValue();
          // XXX: This will replace NaN by 0
          return new Date((long) time);
        } else if (type.isArray() && value instanceof NativeArray) {
          // Make a new java array, and coerce the JS array components
          // to the target (component) type.
          NativeArray array = (NativeArray) value;
          long length = array.getLength();
          Class<?> arrayType = type.getComponentType();
          Object Result = Array.newInstance(arrayType, (int) length);
          for (int i = 0; i < length; ++i) {
            try {
              Array.set(Result, i, coerceType(arrayType, array.get(i, array)));
            } catch (EvaluatorException ee) {
              reportConversionError(value, type);
            }
          }

          return Result;
        } else if (value instanceof Wrapper) {
          value = ((Wrapper) value).unwrap();
          if (type.isInstance(value)) return value;
          reportConversionError(value, type);
        } else if (type.isInterface() && value instanceof Callable) {
          // Try to use function as implementation of Java interface.
          //
          // XXX: Currently only instances of ScriptableObject are
          // supported since the resulting interface proxies should
          // be reused next time conversion is made and generic
          // Callable has no storage for it. Weak references can
          // address it but for now use this restriction.
          if (value instanceof ScriptableObject) {
            ScriptableObject so = (ScriptableObject) value;
            Object key = Kit.makeHashKeyFromPair(COERCED_INTERFACE_KEY, type);
            Object old = so.getAssociatedValue(key);
            if (old != null) {
              // Function was already wrapped
              return old;
            }
            Context cx = Context.getContext();
            Object glue = InterfaceAdapter.create(cx, type, (Callable) value);
            // Store for later retrival
            glue = so.associateValue(key, glue);
            return glue;
          }
          reportConversionError(value, type);
        } else {
          reportConversionError(value, type);
        }
        break;
    }

    return value;
  }
  /**
   * Derive a ranking based on how "natural" the conversion is. The special value CONVERSION_NONE
   * means no conversion is possible, and CONVERSION_NONTRIVIAL signals that more type conformance
   * testing is required. Based on <a
   * href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">"preferred method
   * conversions" from Live Connect 3</a>
   */
  static int getConversionWeight(Object fromObj, Class<?> to) {
    int fromCode = getJSTypeCode(fromObj);

    switch (fromCode) {
      case JSTYPE_UNDEFINED:
        if (to == ScriptRuntime.StringClass || to == ScriptRuntime.ObjectClass) {
          return 1;
        }
        break;

      case JSTYPE_NULL:
        if (!to.isPrimitive()) {
          return 1;
        }
        break;

      case JSTYPE_BOOLEAN:
        // "boolean" is #1
        if (to == Boolean.TYPE) {
          return 1;
        } else if (to == ScriptRuntime.BooleanClass) {
          return 2;
        } else if (to == ScriptRuntime.ObjectClass) {
          return 3;
        } else if (to == ScriptRuntime.StringClass) {
          return 4;
        }
        break;

      case JSTYPE_NUMBER:
        if (to.isPrimitive()) {
          if (to == Double.TYPE) {
            return 1;
          } else if (to != Boolean.TYPE) {
            return 1 + getSizeRank(to);
          }
        } else {
          if (to == ScriptRuntime.StringClass) {
            // native numbers are #1-8
            return 9;
          } else if (to == ScriptRuntime.ObjectClass) {
            return 10;
          } else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) {
            // "double" is #1
            return 2;
          }
        }
        break;

      case JSTYPE_STRING:
        if (to == ScriptRuntime.StringClass) {
          return 1;
        } else if (to.isInstance(fromObj)) {
          return 2;
        } else if (to.isPrimitive()) {
          if (to == Character.TYPE) {
            return 3;
          } else if (to != Boolean.TYPE) {
            return 4;
          }
        }
        break;

      case JSTYPE_JAVA_CLASS:
        if (to == ScriptRuntime.ClassClass) {
          return 1;
        } else if (to == ScriptRuntime.ObjectClass) {
          return 3;
        } else if (to == ScriptRuntime.StringClass) {
          return 4;
        }
        break;

      case JSTYPE_JAVA_OBJECT:
      case JSTYPE_JAVA_ARRAY:
        Object javaObj = fromObj;
        if (javaObj instanceof Wrapper) {
          javaObj = ((Wrapper) javaObj).unwrap();
        }
        if (to.isInstance(javaObj)) {
          return CONVERSION_NONTRIVIAL;
        }
        if (to == ScriptRuntime.StringClass) {
          return 2;
        } else if (to.isPrimitive() && to != Boolean.TYPE) {
          return (fromCode == JSTYPE_JAVA_ARRAY) ? CONVERSION_NONE : 2 + getSizeRank(to);
        }
        break;

      case JSTYPE_OBJECT:
        // Other objects takes #1-#3 spots
        if (to == fromObj.getClass()) {
          // No conversion required
          return 1;
        }
        if (to.isArray()) {
          if (fromObj instanceof NativeArray) {
            // This is a native array conversion to a java array
            // Array conversions are all equal, and preferable to object
            // and string conversion, per LC3.
            return 1;
          }
        } else if (to == ScriptRuntime.ObjectClass) {
          return 2;
        } else if (to == ScriptRuntime.StringClass) {
          return 3;
        } else if (to == ScriptRuntime.DateClass) {
          if (fromObj instanceof NativeDate) {
            // This is a native date to java date conversion
            return 1;
          }
        } else if (to.isInterface()) {
          if (fromObj instanceof Function) {
            // See comments in coerceType
            if (to.getMethods().length == 1) {
              return 1;
            }
          }
          return 11;
        } else if (to.isPrimitive() && to != Boolean.TYPE) {
          return 3 + getSizeRank(to);
        }
        break;
    }

    return CONVERSION_NONE;
  }