package org.dynjs.runtime.linker;
import com.headius.invokebinder.Binder;
import org.dynjs.runtime.*;
import org.dynjs.runtime.linker.java.jsimpl.JSJavaImplementationManager;
import org.projectodd.rephract.java.reflect.CoercionMatrix;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.WeakHashMap;
import static java.lang.invoke.MethodType.methodType;
public class DynJSCoercionMatrix extends CoercionMatrix {
private JSJavaImplementationManager manager;
private WeakHashMap<Class<?>, MethodHandle> samCache = new WeakHashMap<>();
public DynJSCoercionMatrix(JSJavaImplementationManager manager) throws NoSuchMethodException, IllegalAccessException {
this.manager = manager;
Lookup lookup = MethodHandles.lookup();
// Convert JavaScript null and undefined values to Java null
addCoercion(0, Object.class, Types.Null.class, lookup.findStatic(DynJSCoercionMatrix.class, "jsToJavaNull", methodType(Object.class, Object.class)));
// Convert JavaScript objects to Strings
addCoercion(3, String.class, JSObject.class, lookup.findStatic(DynJSCoercionMatrix.class, "objectToString", methodType(String.class, JSObject.class)));
// Byte conversions
// TODO: These belong in Rephract's CoercionMatrix
addCoercion(0, Byte.class, byte.class, MethodHandles.identity(byte.class));
addCoercion(0, Byte.class, Byte.class, MethodHandles.identity(Byte.class));
addCoercion(1, Byte.class, Short.class, lookup.findStatic(DynJSCoercionMatrix.class, "numberToByte", methodType(Byte.class, Number.class)));
addCoercion(1, Byte.class, Integer.class, lookup.findStatic(DynJSCoercionMatrix.class, "numberToByte", methodType(Byte.class, Number.class)));
addCoercion(1, Byte.class, Long.class, lookup.findStatic(DynJSCoercionMatrix.class, "numberToByte", methodType(Byte.class, Number.class)));
addCoercion(2, Byte.class, Double.class, lookup.findStatic(DynJSCoercionMatrix.class, "numberToByte", methodType(Byte.class, Number.class)));
addCoercion(2, Byte.class, Float.class, lookup.findStatic(DynJSCoercionMatrix.class, "numberToByte", methodType(Byte.class, Number.class)));
DynArrayCoercer dynArrayCoercer = new DynArrayCoercer();
addArrayCoercion(1, boolean[].class, DynArray.class, dynArrayCoercer);
addArrayCoercion(1, byte[].class, DynArray.class, dynArrayCoercer);
addArrayCoercion(1, char[].class, DynArray.class, dynArrayCoercer);
addArrayCoercion(1, double[].class, DynArray.class, dynArrayCoercer);
addArrayCoercion(1, float[].class, DynArray.class, dynArrayCoercer);
addArrayCoercion(1, int[].class, DynArray.class, dynArrayCoercer);
addArrayCoercion(1, long[].class, DynArray.class, dynArrayCoercer);
addArrayCoercion(1, short[].class, DynArray.class, dynArrayCoercer);
// Object[] will catch all non-primitive types
addArrayCoercion(2, Object[].class, DynArray.class, dynArrayCoercer);
}
public static String objectToString(JSObject object) {
ExecutionContext context = ThreadContextManager.currentContext();
Object toString = object.get(context, "toString");
if (toString instanceof JSFunction) {
return context.call((JSFunction) toString, object).toString();
}
return null;
}
public static Object jsToJavaNull(Object jsNull) {
return null;
}
public static Byte numberToByte(Number value) {
return value.byteValue();
}
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
@Override
public int isCompatible(Class<?> target, Object actual) {
int superCompat = super.isCompatible(target, actual);
if (superCompat >= 0 && superCompat < 3) {
return superCompat;
}
Class<?> actualClass = actual.getClass();
if (actualClass == Types.UNDEFINED.getClass() || actualClass == Types.NULL.getClass()) {
return 3;
}
if (JSFunction.class.isAssignableFrom(actualClass)) {
if (isSingleAbstractMethod(target) != null) {
return 3;
}
}
return -1;
}
public static boolean coerceToBoolean(Object value) {
return (boolean) DynJSBootstrapper.COERCION_MATRIX.coerceTo( value, boolean.class );
}
public Object coerceTo(Object value, Class<?> toType) {
MethodHandle filter = getFilter(toType, value);
if ( filter == null ) {
return null;
}
try {
return filter.invoke(value);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
@Override
public MethodHandle getFilter(Class<?> target, Object actual) {
int superCompat = super.isCompatible(target, actual);
if (superCompat >= 0 && superCompat < 3) {
return super.getFilter(target, actual);
}
Class<?> actualClass = actual.getClass();
if (actualClass == Types.UNDEFINED.getClass() || actualClass == Types.NULL.getClass()) {
try {
return nullReplacingFilter(target);
} catch (NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
}
if (JSFunction.class.isAssignableFrom(actualClass)) {
try {
String methodName = isSingleAbstractMethod(target);
if (methodName != null) {
return singleAbstractMethod(methodName, target);
}
} catch (NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
}
return MethodHandles.identity(actualClass);
}
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
protected String isSingleAbstractMethod(Class<?> target) {
String methodName = null;
Method[] methods = target.getMethods();
for (int i = 0; i < methods.length; ++i) {
int modifiers = methods[i].getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isAbstract(modifiers)) {
if (methodName == null) {
methodName = methods[i].getName();
} else if (!methodName.equals(methods[i].getName())) {
return null;
}
}
}
return methodName;
}
protected synchronized MethodHandle singleAbstractMethod(String methodName, Class<?> target) throws NoSuchMethodException, IllegalAccessException {
MethodHandle cached = this.samCache.get(target);
if (cached == null) {
Lookup lookup = MethodHandles.lookup();
MethodHandle method = lookup.findStatic(DynJSCoercionMatrix.class, "singleAbstractMethod", methodType(Object.class, JSJavaImplementationManager.class, Class.class, ExecutionContext.class, String.class, JSObject.class));
cached = Binder.from(methodType(target, Object.class))
.insert(0, this.manager)
.insert(1, target)
.insert(2, ThreadContextManager.currentContext())
.insert(3, methodName)
.invoke(method);
this.samCache.put( target, cached );
}
return cached;
}
public static Object singleAbstractMethod(JSJavaImplementationManager manager, Class<?> targetClass, ExecutionContext context, String methodName, JSObject implementation)
throws Exception {
JSObject implObj = new DynObject(context.getGlobalContext());
implObj.put(context, methodName, implementation, false);
return manager.getImplementationWrapper(targetClass, context, implObj);
}
public static MethodHandle nullReplacingFilter(Class<?> target) throws NoSuchMethodException, IllegalAccessException {
Lookup lookup = MethodHandles.lookup();
MethodHandle returnNull = lookup.findStatic(DynJSCoercionMatrix.class, "returnNull", methodType(Object.class, Object.class));
return Binder.from(methodType(target, Object.class))
//.drop(0)
//.insert(0, new Class[]{Object.class}, new Object[]{null})
.invoke(returnNull);
}
public static Object returnNull(Object arg) {
return null;
}
}