/src/sys/java/fan/sys/JavaType.java
Java | 540 lines | 339 code | 68 blank | 133 comment | 107 complexity | 91b58647c04639cd4d72bae52b101d43 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
- //
- // Copyright (c) 2008, Brian Frank and Andy Frank
- // Licensed under the Academic Free License version 3.0
- //
- // History:
- // 23 Nov 08 Brian Frank Creation
- //
- package fan.sys;
- import java.util.HashMap;
- import java.lang.reflect.Modifier;
- import fanx.fcode.*;
- import fanx.util.*;
- /**
- * JavaType wraps a Java class as a Fantom type for FFI reflection.
- * Use Env methods to load JavaTypes.
- */
- public class JavaType
- extends Type
- {
- //////////////////////////////////////////////////////////////////////////
- // Constructor
- //////////////////////////////////////////////////////////////////////////
- /** Must only be called by Env.loadJavaType */
- JavaType(Class cls)
- {
- if (cls.isArray() && cls.getComponentType().isPrimitive())
- {
- this.podName = "[java]fanx.interop";
- this.typeName = FanStr.capitalize(cls.getComponentType().getSimpleName()) + "Array";
- }
- else
- {
- // if array, get component type x
- Class x = cls;
- int rank = 0;
- while (x.isArray()) { rank++; x = x.getComponentType(); }
- // parse packageName and className
- String fullName = x.getName();
- String packName = "";
- String typeName = fullName;
- int dot = fullName.lastIndexOf('.');
- if (dot > 0)
- {
- packName = fullName.substring(0, dot);
- typeName = fullName.substring(dot+1);
- }
- // if we have an array rank, prefix "["
- for (int i=0; i<rank; ++i) typeName = "[" + typeName;
- // now we have our Fantom pod/type names
- this.podName = "[java]" + packName;
- this.typeName = typeName;
- }
- this.cls = cls;
- }
- //////////////////////////////////////////////////////////////////////////
- // Type
- //////////////////////////////////////////////////////////////////////////
- public Pod pod() { return null; }
- public String podName() { return podName; }
- public String name() { return typeName; }
- public String qname() { return podName + "::" + typeName; }
- public String signature() { return qname(); }
- int flags() { return init().flags; }
- public Type base() { return init().base; }
- public List mixins() { return init().mixins; }
- public List inheritance() { return init().inheritance; }
- public boolean is(Type type)
- {
- type = type.toNonNullable();
- if (type == Sys.ObjType) return true;
- return type.toClass().isAssignableFrom(toClass());
- }
- public boolean isVal() { return false; }
- public final boolean isNullable() { return false; }
- public final synchronized Type toNullable()
- {
- if (nullable == null) nullable = new NullableType(this);
- return nullable;
- }
- public List fields() { return initSlots().fields; }
- public List methods() { return initSlots().methods; }
- public Method method(String name, boolean checked)
- {
- // check if slot is overloaded by both field and method
- Slot slot = slot(name, checked);
- if (slot instanceof Field)
- {
- Field f = (Field)slot;
- if (f.overload != null) return f.overload;
- }
- return (Method)slot;
- }
- public List slots() { return initSlots().slots; }
- public Slot slot(String name, boolean checked)
- {
- Slot slot = (Slot)initSlots().slotsByName.get(name);
- if (slot != null) return slot;
- if (checked) throw UnknownSlotErr.make(qname() + "." + name);
- return null;
- }
- public List facets() { return Facets.empty().list(); }
- public Facet facet(Type t, boolean c) { return Facets.empty().get(t, c); }
- public String doc() { return null; }
- public boolean javaRepr() { return false; }
- private RuntimeException unsupported() { return new UnsupportedOperationException(); }
- //////////////////////////////////////////////////////////////////////////
- // Reflection
- //////////////////////////////////////////////////////////////////////////
- /**
- * Get the Java class which represents this type.
- */
- public Class toClass() { return cls; }
- /**
- * Init is responsible for lazily initialization of type
- * level information: flags, base, mixins, and iinheritance.
- */
- private JavaType init()
- {
- if (flags != -1) return this;
- try
- {
- // find Java class
- Class cls = toClass();
- // flags
- flags = FanUtil.classModifiersToFanFlags(cls.getModifiers());
- if (cls.isAnnotation()) flags |= FConst.Const;
- // superclass is base class
- Class superclass = cls.getSuperclass();
- if (superclass != null) base = toFanType(superclass);
- else base = Sys.ObjType;
- // interfaces are mixins
- Class[] interfaces = cls.getInterfaces();
- mixins = new List(Sys.TypeType, interfaces.length);
- for (int i=0; i<interfaces.length; ++i)
- mixins.add(toFanType(interfaces[i]));
- mixins = (List)mixins.toImmutable();
- // inheritance
- inheritance = ClassType.inheritance(this);
- }
- catch (Exception e)
- {
- System.out.println("ERROR: JavaType.init: " + this);
- e.printStackTrace();
- }
- return this;
- }
- /**
- * Reflect the Java class to map is members to Fantom slots.
- */
- private synchronized JavaType initSlots()
- {
- // if already initialized short circuit
- if (slots != null) return this;
- // reflect Java members
- java.lang.reflect.Field[] jfields = toClass().getFields();
- java.lang.reflect.Method[] jmethods = toClass().getMethods();
- // allocate Fantom reflection structurs
- List slots = new List(Sys.SlotType, jfields.length+jmethods.length+4);
- List fields = new List(Sys.FieldType, jfields.length);
- List methods = new List(Sys.MethodType, jfields.length+4);
- HashMap slotsByName = new HashMap();
- // map the fields
- for (int i=0; i<jfields.length; ++i)
- {
- Field f = toFan(jfields[i]);
- slots.add(f);
- fields.add(f);
- slotsByName.put(f.name(), f);
- }
- // map the methods
- for (int i=0; i<jmethods.length; ++i)
- {
- // check if we already have a slot by this name
- java.lang.reflect.Method j = jmethods[i];
- Slot existing = (Slot)slotsByName.get(j.getName());
- // if this method overloads a field
- if (existing instanceof Field)
- {
- // if this is the first method overload over
- // the field then create a link via Field.overload
- Field x = (Field)existing;
- if (x.overload == null)
- {
- Method m = toFan(j);
- x.overload = m;
- methods.add(m);
- continue;
- }
- // otherwise set existing to first method and fall-thru to next check
- existing = x.overload;
- }
- // if this method overloads another method then all
- // we do is add this version to our Method.reflect
- if (existing instanceof Method)
- {
- Method x = (Method)existing;
- java.lang.reflect.Method [] temp = new java.lang.reflect.Method[x.reflect.length+1];
- System.arraycopy(x.reflect, 0, temp, 0, x.reflect.length);
- temp[x.reflect.length] = j;
- x.reflect = temp;
- continue;
- }
- // if we've made it here this method does not overload
- // either a field or method, so we can simply map it
- Method m = toFan(j);
- slots.add(m);
- methods.add(m);
- slotsByName.put(m.name(), m);
- }
- // finish
- this.slots = (List)slots.toImmutable();
- this.fields = (List)fields.toImmutable();
- this.methods = (List)methods.toImmutable();
- this.slotsByName = slotsByName;
- return this;
- }
- /**
- * Map a Java Field to a Fantom field.
- */
- private Field toFan(java.lang.reflect.Field java)
- {
- Type parent = toFanType(java.getDeclaringClass());
- String name = java.getName();
- int flags = FanUtil.memberModifiersToFanFlags(java.getModifiers());
- Facets facets = Facets.empty();
- Type of = toFanType(java.getType());
- // map Java transients to facets
- if (Modifier.isTransient(java.getModifiers()))
- facets = Facets.makeTransient();
- // map Java enum constants as Fantom enum constants
- if (java.isEnumConstant()) flags |= FConst.Enum;
- Field fan = new Field(parent, name, flags, facets, -1, of);
- fan.reflect = java;
- return fan;
- }
- /**
- * Map a Java Method to a Fantom Method.
- */
- private Method toFan(java.lang.reflect.Method java)
- {
- Type parent = toFanType(java.getDeclaringClass());
- String name = java.getName();
- int flags = FanUtil.memberModifiersToFanFlags(java.getModifiers());
- Facets facets = Facets.empty();
- Type ret = toFanType(java.getReturnType());
- Class[] paramClasses = java.getParameterTypes();
- List params = new List(Sys.ParamType, paramClasses.length);
- for (int i=0; i<paramClasses.length; ++i)
- {
- Param param = new Param("p"+i, toFanType(java.getDeclaringClass()), 0);
- params.add(param);
- }
- Method fan = new Method(parent, name, flags, facets, -1, ret, ret, params.ro());
- fan.reflect = new java.lang.reflect.Method[] { java };
- return fan;
- }
- //////////////////////////////////////////////////////////////////////////
- // Dynamic Resolution and Invocation
- //////////////////////////////////////////////////////////////////////////
- /**
- * Create the object using its default constructor.
- */
- public Object make(List args)
- {
- // right now we don't support constructors with arguments
- if (args != null && args.sz() > 0)
- throw UnsupportedErr.make("Cannot call make with args on Java type: " + this);
- // route to Class.newInstance
- try { return toClass().newInstance(); }
- catch (Exception e) { throw Err.make(e); }
- }
- /**
- * Trap for Field.get against Java type.
- */
- static Object get(Field f, Object instance)
- throws Exception
- {
- java.lang.reflect.Field j = f.reflect;
- Class t = j.getType();
- if (t.isPrimitive())
- {
- if (t == int.class) return Long.valueOf(j.getLong(instance));
- if (t == byte.class) return Long.valueOf(j.getLong(instance));
- if (t == short.class) return Long.valueOf(j.getLong(instance));
- if (t == char.class) return Long.valueOf(j.getLong(instance));
- if (t == float.class) return Double.valueOf(j.getDouble(instance));
- }
- return coerceFromJava(j.get(instance));
- }
- /**
- * Trap for Field.set against Java type.
- */
- static void set(Field f, Object instance, Object val)
- throws Exception
- {
- java.lang.reflect.Field j = f.reflect;
- Class t = j.getType();
- if (t.isPrimitive())
- {
- if (t == int.class) { j.setInt(instance, ((Number)val).intValue()); return; }
- if (t == byte.class) { j.setByte(instance, ((Number)val).byteValue()); return; }
- if (t == short.class) { j.setShort(instance, ((Number)val).shortValue()); return; }
- if (t == char.class) { j.setChar(instance, (char)((Number)val).intValue()); return; }
- if (t == float.class) { j.setFloat(instance, ((Number)val).floatValue()); return; }
- }
- j.set(instance, coerceToJava(val, t));
- }
- /**
- * Trap for Method.invoke against Java type.
- */
- static Object invoke(Method m, Object instance, Object[] args)
- throws Exception
- {
- // resolve the method to use with given arguments
- java.lang.reflect.Method j = resolve(m, args);
- // coerce the arguments
- Class[] params = j.getParameterTypes();
- for (int i=0; i<args.length; ++i)
- args[i] = coerceToJava(args[i], params[i]);
- // invoke the method via reflection and coerce result back to Fan
- return coerceFromJava(j.invoke(instance, args));
- }
- /**
- * Given a set of arguments try to resolve the best method to
- * use for reflection. The overloaded methods are stored in the
- * Method.reflect array.
- */
- static java.lang.reflect.Method resolve(Method m, Object[] args)
- {
- // if only one method then this is easy; defer argument
- // checking until we actually try to invoke it
- java.lang.reflect.Method[] reflect = m.reflect;
- if (reflect.length == 1) return reflect[0];
- // find best match
- java.lang.reflect.Method best = null;
- for (int i=0; i<reflect.length; ++i)
- {
- java.lang.reflect.Method x = reflect[i];
- Class[] params = x.getParameterTypes();
- if (!argsMatchParams(args, params)) continue;
- if (best == null) { best = x; continue; }
- if (isMoreSpecific(best, x)) continue;
- if (isMoreSpecific(x, best)) { best = x; continue; }
- throw ArgErr.make("Ambiguous method call '" + m.name + "'");
- }
- if (best != null) return best;
- // no matches
- throw ArgErr.make("No matching method '" + m.name + "' for arguments");
- }
- /**
- * Return if given arguments can be used against the specified
- * parameter types. We have to take into account that we might
- * coercing the arguments from their Fantom represention to Java.
- */
- static boolean argsMatchParams(Object[] args, Class[] params)
- {
- if (args.length != params.length) return false;
- for (int i=0; i<args.length; ++i)
- if (!argMatchesParam(args[i], params[i])) return false;
- return true;
- }
- /**
- * Return if given argument can be used against the specified
- * parameter type. We have to take into account that we might
- * coercing the arguments from their Fantom represention to Java.
- */
- static boolean argMatchesParam(Object arg, Class param)
- {
- // do simple instance of check
- if (param.isInstance(arg)) return true;
- // check implicit primitive coercions
- if (param.isPrimitive())
- {
- // its either boolean, char/numeric
- if (param == boolean.class) return arg instanceof Boolean;
- return arg instanceof Number;
- }
- // check implicit array coercions
- if (param.isArray())
- {
- Class ct = param.getComponentType();
- if (ct.isPrimitive()) return false;
- return arg instanceof List;
- }
- // no coersion to match
- return false;
- }
- /**
- * Given a two of overloaed methods find the most specific method
- * according to Java Language Specification 15.11.2.2. The "informal
- * intuition" rule is that a method is more specific than another
- * if the first could be could be passed onto the second one.
- */
- static boolean isMoreSpecific(java.lang.reflect.Method a, java.lang.reflect.Method b)
- {
- Class[] ap = a.getParameterTypes();
- Class[] bp = b.getParameterTypes();
- for (int i=0; i<ap.length; ++i)
- if (!bp[i].isAssignableFrom(ap[i])) return false;
- return true;
- }
- /**
- * Coerce the specified Fantom representation to the Java class.
- */
- static Object coerceToJava(Object val, Class expected)
- {
- if (expected == int.class) return Integer.valueOf(((Number)val).intValue());
- if (expected == byte.class) return Byte.valueOf(((Number)val).byteValue());
- if (expected == short.class) return Short.valueOf(((Number)val).shortValue());
- if (expected == char.class) return Character.valueOf((char)((Number)val).intValue());
- if (expected == float.class) return Float.valueOf(((Number)val).floatValue());
- if (expected.isArray())
- {
- Class ct = expected.getComponentType();
- if (val instanceof List) return ((List)val).asArray(ct);
- }
- return val;
- }
- /**
- * Coerce a Java object to its Fantom representation.
- */
- static Object coerceFromJava(Object val)
- {
- if (val == null) return null;
- Class t = val.getClass();
- if (t == Integer.class) return Long.valueOf(((Integer)val).longValue());
- if (t == Byte.class) return Long.valueOf(((Byte)val).longValue());
- if (t == Short.class) return Long.valueOf(((Short)val).longValue());
- if (t == Character.class) return Long.valueOf(((Character)val).charValue());
- if (t == Float.class) return Double.valueOf(((Float)val).doubleValue());
- if (t.isArray())
- {
- Class ct = t.getComponentType();
- if (ct.isPrimitive()) return val;
- return new List(FanUtil.toFanType(ct, true), (Object[])val);
- }
- return val;
- }
- //////////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////////
- /**
- * Map a Fantom qname to a Java classname:
- * [java]java.util::Date -> java.util.Date
- */
- static String toClassName(String podName, String typeName)
- {
- return FanUtil.toJavaClassName(podName, typeName);
- }
- /**
- * Map Java class to Fantom type.
- */
- public static Type toFanType(Class cls)
- {
- return FanUtil.toFanType(cls, true);
- }
- //////////////////////////////////////////////////////////////////////////
- // Fields
- //////////////////////////////////////////////////////////////////////////
- private final Class cls; // ctor
- private String podName; // ctor
- private String typeName; // ctor
- private Type nullable; // toNullable()
- private int flags = -1; // init()
- private Type base; // init()
- private List mixins; // init()
- private List inheritance; // init()
- private List fields; // initSlots()
- private List methods; // initSlots()
- private List slots; // initSlots()
- private HashMap slotsByName; // initSlots() - String:Slot
- }