fan-1.0 /src/sys/java/fan/sys/JavaType.java

Language Java Lines 540
MD5 Hash 91b58647c04639cd4d72bae52b101d43 Estimated Cost $8,677 (why?)
Repository https://bitbucket.org/bedlaczech/fan-1.0 View Raw File View Project SPDX
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
//
// 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

}
Back to Top