package jdk.nashorn.internal.runtime.linker;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.JSType.isString;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import jdk.dynalink.linker.support.TypeUtilities;
import jdk.nashorn.internal.runtime.ConsString;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ScriptObject;
final class JavaArgumentConverters {
private static final MethodHandle TO_BOOLEAN = findOwnMH("toBoolean", Boolean.class, Object.class);
private static final MethodHandle TO_STRING = findOwnMH("toString", String.class, Object.class);
private static final MethodHandle TO_DOUBLE = findOwnMH("toDouble", Double.class, Object.class);
private static final MethodHandle TO_NUMBER = findOwnMH("toNumber", Number.class, Object.class);
private static final MethodHandle TO_LONG = findOwnMH("toLong", Long.class, Object.class);
private static final MethodHandle TO_LONG_PRIMITIVE = findOwnMH("toLongPrimitive", long.class, Object.class);
private static final MethodHandle TO_CHAR = findOwnMH("toChar", Character.class, Object.class);
private static final MethodHandle TO_CHAR_PRIMITIVE = findOwnMH("toCharPrimitive", char.class, Object.class);
private JavaArgumentConverters() {
}
static MethodHandle getConverter(final Class<?> targetType) {
return CONVERTERS.get(targetType);
}
@SuppressWarnings("unused")
private static Boolean toBoolean(final Object obj) {
if (obj instanceof Boolean) {
return (Boolean) obj;
}
if (obj == null) {
return null;
}
if (obj == UNDEFINED) {
return null;
}
if (obj instanceof Number) {
final double num = ((Number) obj).doubleValue();
return num != 0 && !Double.isNaN(num);
}
if (isString(obj)) {
return ((CharSequence) obj).length() > 0;
}
if (obj instanceof ScriptObject) {
return true;
}
throw assertUnexpectedType(obj);
}
private static Character toChar(final Object o) {
if (o == null) {
return null;
}
if (o instanceof Number) {
final int ival = ((Number)o).intValue();
if (ival >= Character.MIN_VALUE && ival <= Character.MAX_VALUE) {
return (char) ival;
}
throw typeError("cant.convert.number.to.char");
}
final String s = toString(o);
if (s == null) {
return null;
}
if (s.length() != 1) {
throw typeError("cant.convert.string.to.char");
}
return s.charAt(0);
}
static char toCharPrimitive(final Object obj0) {
final Character c = toChar(obj0);
return c == null ? (char)0 : c;
}
static String toString(final Object obj) {
return obj == null ? null : JSType.toString(obj);
}
@SuppressWarnings("unused")
private static Double toDouble(final Object obj0) {
for (Object obj = obj0; ;) {
if (obj == null) {
return null;
} else if (obj instanceof Double) {
return (Double) obj;
} else if (obj instanceof Number) {
return ((Number)obj).doubleValue();
} else if (obj instanceof String) {
return JSType.toNumber((String) obj);
} else if (obj instanceof ConsString) {
return JSType.toNumber(obj.toString());
} else if (obj instanceof Boolean) {
return (Boolean) obj ? 1 : +0.0;
} else if (obj instanceof ScriptObject) {
obj = JSType.toPrimitive(obj, Number.class);
continue;
} else if (obj == UNDEFINED) {
return Double.NaN;
}
throw assertUnexpectedType(obj);
}
}
@SuppressWarnings("unused")
private static Number toNumber(final Object obj0) {
for (Object obj = obj0; ;) {
if (obj == null) {
return null;
} else if (obj instanceof Number) {
return (Number) obj;
} else if (obj instanceof String) {
return JSType.toNumber((String) obj);
} else if (obj instanceof ConsString) {
return JSType.toNumber(obj.toString());
} else if (obj instanceof Boolean) {
return (Boolean) obj ? 1 : +0.0;
} else if (obj instanceof ScriptObject) {
obj = JSType.toPrimitive(obj, Number.class);
continue;
} else if (obj == UNDEFINED) {
return Double.NaN;
}
throw assertUnexpectedType(obj);
}
}
private static Long toLong(final Object obj0) {
for (Object obj = obj0; ;) {
if (obj == null) {
return null;
} else if (obj instanceof Long) {
return (Long) obj;
} else if (obj instanceof Integer) {
return ((Integer)obj).longValue();
} else if (obj instanceof Double) {
final Double d = (Double)obj;
if(Double.isInfinite(d)) {
return 0L;
}
return d.longValue();
} else if (obj instanceof Float) {
final Float f = (Float)obj;
if(Float.isInfinite(f)) {
return 0L;
}
return f.longValue();
} else if (obj instanceof Number) {
return ((Number)obj).longValue();
} else if (isString(obj)) {
return JSType.toLong(obj);
} else if (obj instanceof Boolean) {
return (Boolean)obj ? 1L : 0L;
} else if (obj instanceof ScriptObject) {
obj = JSType.toPrimitive(obj, Number.class);
continue;
} else if (obj == UNDEFINED) {
return null;
}
throw assertUnexpectedType(obj);
}
}
private static AssertionError assertUnexpectedType(final Object obj) {
return new AssertionError("Unexpected type" + obj.getClass().getName() + ". Guards should have prevented this");
}
@SuppressWarnings("unused")
private static long toLongPrimitive(final Object obj0) {
final Long l = toLong(obj0);
return l == null ? 0L : l;
}
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findStatic(MethodHandles.lookup(), JavaArgumentConverters.class, name, MH.type(rtype, types));
}
private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
static {
CONVERTERS.put(Number.class, TO_NUMBER);
CONVERTERS.put(String.class, TO_STRING);
CONVERTERS.put(boolean.class, JSType.TO_BOOLEAN.methodHandle());
CONVERTERS.put(Boolean.class, TO_BOOLEAN);
CONVERTERS.put(char.class, TO_CHAR_PRIMITIVE);
CONVERTERS.put(Character.class, TO_CHAR);
CONVERTERS.put(double.class, JSType.TO_NUMBER.methodHandle());
CONVERTERS.put(Double.class, TO_DOUBLE);
CONVERTERS.put(long.class, TO_LONG_PRIMITIVE);
CONVERTERS.put(Long.class, TO_LONG);
putLongConverter(Byte.class);
putLongConverter(Short.class);
putLongConverter(Integer.class);
putDoubleConverter(Float.class);
}
private static void putDoubleConverter(final Class<?> targetType) {
final Class<?> primitive = TypeUtilities.getPrimitiveType(targetType);
CONVERTERS.put(primitive, MH.explicitCastArguments(JSType.TO_NUMBER.methodHandle(), JSType.TO_NUMBER.methodHandle().type().changeReturnType(primitive)));
CONVERTERS.put(targetType, MH.filterReturnValue(TO_DOUBLE, findOwnMH(primitive.getName() + "Value", targetType, Double.class)));
}
private static void putLongConverter(final Class<?> targetType) {
final Class<?> primitive = TypeUtilities.getPrimitiveType(targetType);
CONVERTERS.put(primitive, MH.explicitCastArguments(TO_LONG_PRIMITIVE, TO_LONG_PRIMITIVE.type().changeReturnType(primitive)));
CONVERTERS.put(targetType, MH.filterReturnValue(TO_LONG, findOwnMH(primitive.getName() + "Value", targetType, Long.class)));
}
@SuppressWarnings("unused")
private static Byte byteValue(final Long l) {
return l == null ? null : l.byteValue();
}
@SuppressWarnings("unused")
private static Short shortValue(final Long l) {
return l == null ? null : l.shortValue();
}
@SuppressWarnings("unused")
private static Integer intValue(final Long l) {
return l == null ? null : l.intValue();
}
@SuppressWarnings("unused")
private static Float floatValue(final Double d) {
return d == null ? null : d.floatValue();
}
}