/** * */ package erjang.m.java; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import erjang.codegen.EFunCG; import junit.extensions.TestDecorator; import kilim.Mailbox; import kilim.Pausable; import erjang.EAtom; import erjang.EBinary; import erjang.EBitString; import erjang.ECons; import erjang.EDouble; import erjang.EFun; import erjang.EFunHandler; import erjang.EInteger; import erjang.EList; import erjang.ENil; import erjang.ENumber; import erjang.EObject; import erjang.EProc; import erjang.EPseudoTerm; import erjang.ERT; import erjang.ESeq; import erjang.ESmall; import erjang.EString; import erjang.ETuple; import erjang.ETuple2; import erjang.ErlangError; import erjang.ErlangException; import erjang.driver.IO; import erjang.m.erlang.ErlProc; public class JavaObject extends EPseudoTerm { public JavaObject testJavaObject() { return this; } static final EAtom am_none = EAtom.intern("none"); static final EAtom am_java_object = EAtom.intern("java_object"); static final EAtom am_badaccess = EAtom.intern("badaccess"); static final EAtom am_badfun = EAtom.intern("badfun"); static final EAtom am_null_pointer_exception = EAtom .intern("null_pointer_exception"); protected static final EAtom am_erlang = EAtom.intern("erlang"); protected static final EAtom am_apply = EAtom.intern("apply"); final Object real_object; final EProc owner; public Object realObject() { return real_object; } @Override public int hashCode() { return real_object.hashCode(); } //TODO: equality operators @Override public EBinary testBinary() { if (real_object instanceof byte[]) { return new EBinary((byte[]) real_object); } if (real_object instanceof String) { return new EBinary(((String) real_object).getBytes(IO.UTF8)); } return null; } @Override public EBitString testBitString() { EBinary bi; if ((bi=testBinary()) != null) { return bi; } if (testCons() == null && testNumber() == null && testTuple() == null && testAtom() == null && testHandle() == null ) return EBinary.EMPTY; return null; } @Override public ENil testNil() { if (real_object == null) return ERT.NIL; if (testSeq() == ERT.NIL) return ERT.NIL; return null; } @Override public EFun testFunction() { EFun fun; if ((fun = testFunction2(0)) != null) { return fun; } if ((fun = testFunction2(1)) != null) { return fun; } return null; } @Override public EFun testFunction2(int nargs) { /** a java.lang.Runnable can be used as a function of 0 arguments */ if ((nargs == 0) && (real_object instanceof Runnable)) { final Runnable r = (Runnable) real_object; return EFunCG.get_fun_with_handler("java.lang.Runnable", "run", 0, new EFunHandler() { @Override public EObject invoke(EProc proc, EObject[] args) throws Pausable { if (proc != owner) throw new ErlangError(ERT.am_badfun, args); r.run(); return ERT.am_ok; } }, getClass().getClassLoader()); } /* * a java.util.Map can be used as a function with 1 argument to get a * value */ if ((nargs == 1) && (real_object instanceof java.util.Map<?, ?>)) { final java.util.Map<?, ?> r = (java.util.Map<?, ?>) real_object; return EFunCG.get_fun_with_handler("java.util.Map", "get", 0, new EFunHandler() { @Override public EObject invoke(EProc self, EObject[] args) throws Pausable { if (self != owner) throw new ErlangError(ERT.am_badfun, args); Object key = JavaObject.unbox(self, Object.class, args[0]); if (r.containsKey(key)) { return new ETuple2(args[0], JavaObject.box(self, r .get(key))); } else { return am_none; } } }, getClass().getClassLoader()); } return null; } @Override public ECons testCons() { return testSeq(); } @Override public ECons testNonEmptyList() { ESeq seq = testSeq(); if (seq == null || seq.isNil()) return null; return seq; } @Override public EAtom testBoolean() { if (real_object == Boolean.TRUE) return ERT.TRUE; if (real_object == Boolean.FALSE) return ERT.FALSE; return null; } @Override public EString testString() { if (real_object instanceof String) { return EString.fromString((String) real_object); } ESeq seq; if ((seq = testSeq()) != null) { return seq.testString(); } return null; } @Override public EAtom testAtom() { if (real_object instanceof String) { String s = (String) real_object; return EAtom.existing_atom(s); } if (real_object instanceof Class) { return EAtom.intern(((Class<?>) real_object).getName()); } return null; } @SuppressWarnings("unchecked") @Override public ESeq testSeq() { if (real_object instanceof CharSequence) { return JavaCharSeq.box(owner, (CharSequence) real_object, 0); } if (real_object instanceof Iterable<?>) { Iterable<?> it = (Iterable<?>) real_object; return JavaIterator.box(owner, it.iterator()); } if (real_object instanceof Iterator<?>) { return JavaIterator.box(owner, ((Iterator<?>) real_object)); } if (real_object instanceof Map<?, ?>) { return JavaMapIterator.box(owner, ((Map) real_object).entrySet() .iterator()); } if (real_object != null && real_object.getClass().isArray()) { return JavaArray.box(owner, real_object, 0); } return null; } @Override public ESmall testSmall() { EInteger iv; if ((iv = testInteger()) != null) { return iv.testSmall(); } return null; } @Override public ENumber testNumber() { EInteger i; if ((i = testInteger()) != null) return i; EDouble d; if ((d = testFloat()) != null) return d; return null; } @Override public EInteger testInteger() { if (real_object instanceof BigInteger) { return ERT.box(((BigInteger) real_object)); } if (real_object instanceof Integer) { return ERT.box(((Integer) real_object).intValue()); } if (real_object instanceof Short) { return ERT.box(((Short) real_object).shortValue()); } if (real_object instanceof Character) { return ERT.box(((Character) real_object).charValue()); } if (real_object instanceof Byte) { return ERT.box(((Byte) real_object).byteValue()); } if (real_object instanceof Long) { return ERT.box(((Long) real_object).longValue()); } return null; } @Override public EDouble testFloat() { if (real_object instanceof Double) { return new EDouble(((Double) real_object).doubleValue()); } return null; } public JavaObject(EProc self, Object object) { real_object = object; owner = self; } public String toString() { return String.valueOf(real_object); }; public static EObject box(EProc self, Object object) { if (object instanceof EObject) { return (EObject) object; } return new JavaObject(self, object); } public static Object[] convert_args(EProc self, Class<?>[] arg_types, ESeq arg_seq) throws IllegalArgumentException { Object[] out = new Object[arg_types.length]; ESeq as_iter = arg_seq; for (int i = 0; i < arg_types.length; i++) { out[i] = JavaObject.unbox(self, arg_types[i], as_iter.head()); as_iter = as_iter.tail(); } return out; } public static Object[] convert_args(EProc self, Class<?>[] arg_types, EObject[] args) throws IllegalArgumentException { Object[] out = new Object[arg_types.length]; for (int i = 0; i < arg_types.length; i++) { out[i] = JavaObject.unbox(self, arg_types[i], args[i]); } return out; } /** this logic is used to map an EObject back to a Java object */ static Object unbox(final EProc self, Class<?> type, EObject val) throws IllegalArgumentException { if (val instanceof JavaObject) { JavaObject jo = (JavaObject) val; if (type.isInstance(jo.real_object)) { return jo.real_object; } else { throw new IllegalArgumentException("cannot convert " + val.getClass().getName() + " to " + type); } } if (type == Object.class || type.isInstance(val)) { // TODO: give each erlang term a "natural" conversion to // java.lang.Object return val; } if (type == Void.class || type == void.class) { return null; } /** unbox to array type */ if (type.isArray()) { ESeq seq; if ((seq = val.testSeq()) != null) { int length = seq.length(); Class<?> componentType = type.getComponentType(); Object arr = Array.newInstance(componentType, length); int index = 0; while (!seq.isNil()) { Object value = JavaObject.unbox(self, componentType, seq .head()); Array.set(arr, index++, value); seq = seq.tail(); } return arr; } ETuple tup; if ((tup = val.testTuple()) != null) { int length = tup.arity(); Class<?> componentType = type.getComponentType(); Object arr = Array.newInstance(componentType, length); for (int index = 0; index < length; index++) { Object value = JavaObject.unbox(self, componentType, tup .elm(index + 1)); Array.set(arr, index, value); } return arr; } } Mapper mapper = type_mapper.get(type); if (mapper != null) { return mapper.map(val); } final EFun ifun; if (type.isInterface() && (ifun = val.testFunction2(3)) != null) { final ClassLoader loader = JavaObject.class .getClassLoader(); return java.lang.reflect.Proxy.newProxyInstance(loader, new Class[] { type }, new InvocationHandler() { @Override public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { final Mailbox<Object> reply = new Mailbox<Object>(1); EFun job = EFunCG.get_fun_with_handler("erlang", "apply", 0, new EFunHandler() { @Override public EObject invoke(EProc proc, EObject[] unused) throws Pausable { EObject aa = JavaObject.box(proc, args); EObject at = JavaObject.box(proc, method.getParameterTypes()); EObject[] call_args = new EObject[] { EAtom.intern(method.getName()), at, aa }; EObject result = ifun.invoke(proc, call_args); Object jresult = JavaObject.unbox(proc, method.getReturnType(), result); if (method.getReturnType() == void.class) { reply.put(ERT.am_ok); } else { reply.put( jresult ); } return ERT.am_ok; } }, loader); EProc proc = new EProc(self.group_leader(), am_erlang, am_apply, EList.make(job, ERT.NIL)); ERT.run(proc); return reply.getb(); } }); } throw new IllegalArgumentException("cannot convert " + val.getClass().getName() + " to " + type); } static Map<Class<?>, Mapper> type_mapper = new HashMap<Class<?>, Mapper>(); interface Mapper { Object map(EObject val); } static { Mapper string_mapper = new Mapper() { public Object map(EObject val) { EString str; if ((str = val.testString()) != null) return str.stringValue(); EAtom am; if ((am = val.testAtom()) != null) return am.getName(); EBinary bi; if ((bi = val.testBinary()) != null) { return new String(bi.toByteArray(), IO.UTF8); } throw new IllegalArgumentException(); } }; type_mapper.put(String.class, string_mapper); type_mapper.put(CharSequence.class, string_mapper); Mapper byte_mapper = new Mapper() { public Object map(EObject val) { ESmall num; // special case: allow converting values -127 ... 155. if ((num = val.testSmall()) != null && num.value >= Byte.MIN_VALUE && num.value <= 255) { return new Byte((byte) num.intValue()); } throw new IllegalArgumentException("cannot convert " + val + " to byte"); } }; type_mapper.put(Byte.class, byte_mapper); type_mapper.put(byte.class, byte_mapper); Mapper short_mapper = new Mapper() { public Object map(EObject val) { ESmall num; if ((num = val.testSmall()) != null && num.value >= Short.MIN_VALUE && num.value <= Short.MAX_VALUE) { return new Short((byte) num.intValue()); } throw new IllegalArgumentException("cannot convert " + val + " to short"); } }; type_mapper.put(Short.class, short_mapper); type_mapper.put(short.class, short_mapper); Mapper int_mapper = new Mapper() { public Object map(EObject val) { ESmall num; if ((num = val.testSmall()) != null) { return new Integer(num.intValue()); } throw new IllegalArgumentException("cannot convert " + val + " to int"); } }; type_mapper.put(Integer.class, int_mapper); type_mapper.put(int.class, int_mapper); Mapper long_mapper = new Mapper() { public Object map(EObject val) { EInteger num; if ((num = val.testInteger()) != null) { return new Long(num.longValue()); } throw new IllegalArgumentException("cannot convert " + val + " to long"); } }; type_mapper.put(Long.class, long_mapper); type_mapper.put(long.class, long_mapper); Mapper double_mapper = new Mapper() { public Object map(EObject val) { ENumber num; if ((num = val.testNumber()) != null) { return new Double(num.doubleValue()); } throw new IllegalArgumentException("cannot convert " + val + " to double"); } }; type_mapper.put(Double.class, double_mapper); type_mapper.put(double.class, double_mapper); Mapper float_mapper = new Mapper() { public Object map(EObject val) { ENumber num; if ((num = val.testNumber()) != null) { return new Float(num.doubleValue()); } throw new IllegalArgumentException("cannot convert " + val + " to float"); } }; type_mapper.put(Float.class, float_mapper); type_mapper.put(float.class, float_mapper); Mapper bool_mapper = new Mapper() { public Object map(EObject val) { EAtom bool; if ((bool = val.testBoolean()) != null) { if (bool == ERT.TRUE) return Boolean.TRUE; if (bool == ERT.FALSE) return Boolean.FALSE; } throw new IllegalArgumentException(); } }; type_mapper.put(boolean.class, bool_mapper); type_mapper.put(Boolean.class, bool_mapper); } /** * return a fun that will call a Java method; used when calling * Object:doThis(a,b,c) */ public EFun resolve_fun(final EAtom f, final int arity) { // TODO: we can make this much much faster! return EFunCG.get_fun_with_handler("java.lang.Object", f.toString(), arity, new EFunHandler() { @Override public EObject invoke(EProc proc, EObject[] args) throws Pausable { if (real_object == null) { throw new ErlangError(am_null_pointer_exception); } Method[] methods = real_object.getClass().getMethods(); return choose_and_invoke_method(proc, real_object, f, args, methods, false); } }, JavaObject.class.getClassLoader()); } public static EObject choose_and_invoke_method(EProc self, Object target, final EAtom method_name, EObject[] args, Method[] methods, boolean static_only) { for (int i = 0; i < methods.length; i++) { // TODO: handle reflective invocation of Pausable methods Method m = methods[i]; int modifier = m.getModifiers(); boolean is_static = (Modifier.STATIC & modifier) == Modifier.STATIC; if (is_static != static_only) continue; if (!m.getName().equals(method_name.getName())) continue; Class<?>[] pt = m.getParameterTypes(); if (pt.length != args.length) continue; Object[] a; try { a = convert_args(self, pt, args); } catch (IllegalArgumentException e) { // TODO: make this a null check continue; } // point-of-no-return Object result; try { result = m.invoke(target, a); } catch (IllegalArgumentException e) { throw ERT.badarg(args); } catch (IllegalAccessException e) { throw new ErlangError(am_badaccess, args); } catch (InvocationTargetException e) { Throwable te = e.getTargetException(); if (te instanceof ErlangException) { throw (ErlangException) te; } else { ETuple reason = ETuple.make(EAtom.intern(te.getClass() .getName()), EString.fromString(te.getMessage())); throw new ErlangError(reason, args); } } if (m.getReturnType() == Void.TYPE) { return ERT.am_ok; } return JavaObject.box(self, result); } throw new ErlangError(am_badfun, args); } public static EObject choose_and_invoke_constructor(EProc self, EObject[] args, Constructor<?>[] cons) { for (int i = 0; i < cons.length; i++) { // TODO: handle reflective invocation of Pausable methods Constructor<?> m = cons[i]; Class<?>[] pt = m.getParameterTypes(); if (pt.length != args.length) continue; Object[] a; try { a = convert_args(self, pt, args); } catch (IllegalArgumentException e) { // TODO: make this a null check continue; } // point-of-no-return Object result; try { result = m.newInstance(a); } catch (InstantiationException e) { throw ERT.badarg(args); } catch (IllegalArgumentException e) { throw ERT.badarg(args); } catch (IllegalAccessException e) { throw new ErlangError(am_badaccess, args); } catch (InvocationTargetException e) { Throwable te = e.getTargetException(); if (te instanceof ErlangException) { throw (ErlangException) te; } else { ETuple reason = ETuple.make(EAtom.intern(te.getClass() .getName()), EString.fromString(te.getMessage())); throw new ErlangError(reason, args); } } return JavaObject.box(self, result); } throw new ErlangError(am_badfun, args); } }