Fantom

 

//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   17 Nov 08  Brian Frank  Creation
//   13 Dec 08  Brian Frank  Port from Java to Fantom using FFI
//

using compiler
using [java] java.lang
using [java] java.lang.reflect::Constructor as JCtor
using [java] java.lang.reflect::Field as JField
using [java] java.lang.reflect::Method as JMethod
using [java] java.lang.reflect::Modifier as JModifier
using [java] fanx.util

**
** JavaReflect provides Java reflection utilities.
** It encapsulates the FFI calls out to Java.
**
** TODO: this code is obsolete, it has been replaced by JavaDasmLoader
** as of Feb 2012; keep around until we are sure new stuff is correct
** in case we need to compare reflection and disassembler results
** side-by-side
**
internal class JavaReflect
{
  **
  ** Map class meta-data and Java members to Fantom slots
  ** for the specified JavaType
  **
  static Void loadType(JavaType self, Str:CSlot slots)
  {
    // map to Java class
    cls := toJavaClass(self)

    // map superclass
    if (cls.getSuperclass != null)
      self.base = toFanType(self.bridge, cls.getSuperclass).toNonNullable

    // map interfaces to mixins
    mixins := CType[,]
    cls.getInterfaces.each |Class c|
    {
      try
        mixins.add(toFanType(self.bridge, c).toNonNullable)
      catch (UnknownTypeErr e)
        errUnknownType(e)
    }
    if (cls.isAnnotation) mixins.add(self.ns.facetType)
    self.mixins = mixins

    // map Java modifiers to Fantom flags
    self.flags = toClassFlags(cls.getModifiers)

    // map Annotation element methods as fields so that
    // it looks like a Fantom facet
    if (cls.isAnnotation) mapAnnotationFields(cls, self, slots)

    // map Java fields to CSlots (public and protected)
    findFields(cls).each |JField j| { mapField(self, slots, j) }

    // map Java methods to CSlots (public and protected)
    findMethods(cls).each |JMethod j| { mapMethod(self, slots, j) }

    // map Java constructors to CSlots
    cls.getDeclaredConstructors.each |JCtor j| { mapCtor(self, slots, j) }

    // merge in sys::Obj slots
    self.ns.objType.slots.each |CSlot s|
    {
      if (s.isCtor) return
      if (slots[s.name] == null) slots[s.name] = s
    }
  }

  **
  ** Reflect the public and protected fields which Java
  ** reflection makes very difficult.
  **
  static JField[] findFields(Class? cls)
  {
    acc := JField:JField[:]

    // first add all the public fields
    cls.getFields.each |JField j| { acc[j] = j }

    // do protected fields working back up the hierarchy; don't
    // worry about interfaces b/c they can declare protected members
    while (cls != null)
    {
      cls.getDeclaredFields.each |JField j|
      {
        if (!JModifier.isProtected(j.getModifiers)) return
        if (acc[j] == null) acc[j] = j
      }
      cls = cls.getSuperclass
    }

    return acc.vals
  }

  **
  ** Reflect the public and protected methods which Java
  ** reflection makes very difficult.
  **
  static JMethod[] findMethods(Class? cls)
  {
    acc := Str:JMethod[:]

    // first add all the public methods
    cls.getMethods.each |JMethod j| { acc[jmethodKey(j)] = j }

    // do protected methods working back up the hierarchy; don't
    // worry about interfaces b/c they can declare protected members
    while (cls != null)
    {
      cls.getDeclaredMethods.each |JMethod j|
      {
        if (!JModifier.isProtected(j.getModifiers)) return
        key := jmethodKey(j)
        if (acc[key] == null) acc[key] = j
      }
      cls = cls.getSuperclass
    }

    return acc.vals
  }

  **
  ** Create hash key for java.lang.reflect.Method which takes
  ** into account name and parameter signatures but not declaring class
  **
  static Str jmethodKey(JMethod method)
  {
    s := StrBuf()
    s.add(method.getName).addChar('(')
    method.getParameterTypes.each |Class p, Int i|
    {
      if (i > 0) s.addChar(',')
      s.add(p.getName)
    }
    s.addChar(')')
    return s.toStr
  }

//////////////////////////////////////////////////////////////////////////
// Java Member -> Fantom CSlot
//////////////////////////////////////////////////////////////////////////

  static Void mapField(JavaType self, Str:CSlot slots, JField java)
  {
    mods := java.getModifiers
    if (!JModifier.isPublic(mods) && !JModifier.isProtected(mods)) return
    try
    {
      fan := JavaField(
        self,
        java.getName,
        toMemberFlags(mods),
        toFanType(self.bridge, java.getType))
      slots.set(fan.name, fan)
    }
    catch (UnknownTypeErr e) errUnknownType(e)
  }

  static Void mapMethod(JavaType self, Str:CSlot slots, JMethod java)
  {
    mods := java.getModifiers
    if (!JModifier.isPublic(mods) && !JModifier.isProtected(mods)) return
    try
    {
      fan := JavaMethod(
        self,
        java.getName,
        toMemberFlags(mods),
        toFanType(self.bridge, java.getReturnType))
      fan.setParamTypes(toFanTypes(self.bridge, java.getParameterTypes))
      addSlot(slots, fan.name, fan)
    }
    catch (UnknownTypeErr  e) errUnknownType(e)
  }

  static Void mapCtor(JavaType self, Str:CSlot slots, JCtor java)
  {
    mods := java.getModifiers
    if (!JModifier.isPublic(mods) && !JModifier.isProtected(mods)) return
    try
    {
      fan := JavaMethod(
        self,
        "<init>",
        toMemberFlags(mods).or(FConst.Ctor),
        self)
      fan.setParamTypes(toFanTypes(self.bridge, java.getParameterTypes))
      addSlot(slots, fan.name, fan)
    }
    catch (UnknownTypeErr  e) errUnknownType(e)
  }

  static Void addSlot(Str:CSlot slots, Str name, JavaMethod m)
  {
    // put the first one into the slot, and add
    // the overloads as linked list on that
    JavaSlot? x := slots.get(name)
    if (x == null) { slots.add(name, m); return }

    // check the linked list for methods with the exact same
    // signature (this can happen by inheriting abstract methods
    // from multiple interfaces)
    for (p := x; p != null; p = p.next)
      if (p is JavaMethod && m.sigsEqual(p))
        return

    // create linked list of overloads
    m.next = x.next
    x.next = m
  }

//////////////////////////////////////////////////////////////////////////
// Annotations
//////////////////////////////////////////////////////////////////////////

  static Void mapAnnotationFields(Class cls, JavaType self, Str:CSlot slots)
  {
    // Java annotations declare their "elements" as abstract public
    // methods, but in Fantom facets are declared with const fields;
    // so we here we fake it out so that a Java annotation type looks
    // like a Fantom facet from the compiler's perspective
    cls.getDeclaredMethods.each |JMethod m|
    {
      if (!JModifier.isPublic(m.getModifiers)) return
      if (!JModifier.isAbstract(m.getModifiers)) return
      try
      {

        fan := JavaField(
          self,
          m.getName,
          FConst.Public.or(FConst.Const),
          toAnnotationType(self, m))
        slots.set(fan.name, fan)
      }
      catch (UnknownTypeErr e) errUnknownType(e)
    }
  }

  private static CType toAnnotationType(JavaType self, JMethod m)
  {
    ns := self.ns
    switch (m.getReturnType.getName)
    {
      case "java.lang.Class":    return ns.typeType
      case "[Ljava.lang.Class;": return ns.typeType.toListOf
      case "[Z":                 return ns.boolType.toListOf
      case "[B":
      case "[S":
      case "[I":
      case "[J":                 return ns.intType.toListOf
      case "[F":
      case "[D":                 return ns.floatType.toListOf
      default:                   return toFanType(self.bridge, m.getReturnType)
    }
  }

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  **
  ** Use reflection to map a JavaType to its Java class.
  **
  static Class toJavaClass(JavaType t)
  {
    Class.forName(t.toJavaClassName)
  }

  **
  ** Map an array of Java classes to their CType representations.
  **
  static CType[] toFanTypes(JavaBridge bridge, Class[] cls)
  {
    cls.map |Class c->CType| { toFanType(bridge, c) }
  }

  **
  ** Map a Java classes to its CType representation.
  **
  static CType toFanType(JavaBridge bridge, Class cls, Bool multiDim := false)
  {
    ns := bridge.ns
    primitives := bridge.primitives

    // primitives
    if (cls.isPrimitive)
    {
      switch (cls.getName)
      {
        case "void":    return ns.voidType
        case "boolean": return multiDim ? primitives.booleanType : ns.boolType
        case "long":    return multiDim ? primitives.longType    : ns.intType
        case "double":  return multiDim ? primitives.doubleType  : ns.floatType
        case "int":     return primitives.intType
        case "byte":    return primitives.byteType
        case "short":   return primitives.shortType
        case "char":    return primitives.charType
        case "float":   return primitives.floatType
      }
      throw Err(cls.toStr)
    }

    // arrays [java]foo.bar::[Baz
    if (cls.isArray)
    {
      compCls := cls.getComponentType

      // if a primary array
      if (compCls.isPrimitive && !multiDim)
      {
        switch (cls.getName)
        {
          case "[Z": return ns.resolveType("[java]fanx.interop::BooleanArray?")
          case "[B": return ns.resolveType("[java]fanx.interop::ByteArray?")
          case "[S": return ns.resolveType("[java]fanx.interop::ShortArray?")
          case "[C": return ns.resolveType("[java]fanx.interop::CharArray?")
          case "[I": return ns.resolveType("[java]fanx.interop::IntArray?")
          case "[J": return ns.resolveType("[java]fanx.interop::LongArray?")
          case "[F": return ns.resolveType("[java]fanx.interop::FloatArray?")
          case "[D": return ns.resolveType("[java]fanx.interop::DoubleArray?")
        }
        throw Err(cls.getName)
      }

      // return "[java] foo.bar::[Baz"
      comp := toFanType(bridge, compCls, true).toNonNullable
      if (comp isnot JavaType) throw Err("Not JavaType: $compCls -> $comp")
      return ((JavaType)comp).toArrayOf.toNullable
    }

    // check for direct mappings of Obj/Str/Decimal (considered nullable)
    if (!multiDim)
    {
      direct := objectClassToDirectFanType(ns, cls.getName)
      if (direct != null) return direct.toNullable
    }

    // anything in fan.sys package is really a sys pod type
    package := cls.getPackage.getName
    name := cls.getName[cls.getName.indexr(".")+1..-1]
    if (package == "fan.sys") return ns.resolveType("sys::$name?")

    // Java FFI
    sig := "[java]${package}::${name}?"
    return ns.resolveType(sig)
  }

  **
  ** If the specified Java classname maps directly to a Fantom type
  ** then return it, otherwise null.  Direct mappings are 'sys::Obj',
  ** 'sys::Str', and 'sys::Decimal' - this method only handles
  ** object classes, not primitives like boolean, long, and double.
  **
  internal static CType? objectClassToDirectFanType(CNamespace ns, Str clsname)
  {
    switch (clsname)
    {
      case "java.lang.Object": return ns.objType
      case "java.lang.String":  return ns.strType
      case "java.math.BigDecimal": return ns.decimalType
      default: return null
    }
  }

  **
  ** Convert Java class modifiers to Fantom flags.
  **
  static Int toClassFlags(Int modifiers)
  {
    FanUtil.classModifiersToFanFlags(modifiers)
  }

  **
  ** Convert Java member modifiers to Fantom flags.
  **
  static Int toMemberFlags(Int modifiers)
  {
    FanUtil.memberModifiersToFanFlags(modifiers)
  }

  **
  ** Handle an error during the Java mapping process - if we
  ** can't map a given Java member we just output a warning
  ** rather than have the whole compilation fail.
  **
  static Void errUnknownType(UnknownTypeErr e)
  {
    // just print a warning and ignore problematic APIs
    echo("WARNING: Cannot map Java type: $e.msg")
  }
}