<commit_before>//
// JavaSiteForm.scala -- Scala object JavaSiteForm and class JavaClassProxy
// Project OrcScala
//
// $Id$
//
// Created by jthywiss on May 30, 2010.
//
// Copyright (c) 2010 The University of Texas at Austin. All rights reserved.
//
// Use and redistribution of this file is governed by the license terms in
// the LICENSE file found in the project's top-level directory and also found at
// URL: http://orc.csres.utexas.edu/license.shtml .
//

package orc.values.sites

import orc.TokenAPI
import orc.oil.nameless.Type // FIXME: Typechecker should operate on named types instead
import orc.values.Value

/**
 * Services (such as name resolution) for use of plain old Java classes
 * as Orc sites.
 *
 * @author jthywiss
 */
object JavaSiteForm extends SiteForm {
  def resolve(name: String) = {
    new JavaClassProxy(loadClass(name))
  }
  private def loadClass(name:String) = getClass().getClassLoader().loadClass(name) //TODO:FIXME: This should use the OrcAPI's loadClass, and the classpath from the OrcOptions
}

/**
 * Wrapper for a plain old Java class as an Orc site
 *
 * @author jthywiss
 */
class JavaClassProxy(val javaClass: Class[_]) extends Site {
  override lazy val name = javaClass.getName()
  override def orcType(argTypes: List[Type]) = null //TODO:FIXME: Implement this
  override def call(args: List[Value], token: TokenAPI) { println("Java site "+name+" called") }  //TODO:FIXME: Implement this
}<commit_msg>Snapshot of Orc-Java bridge work to date -- mostly works for method invocation. Remaining major todos: 1) constructors, 2) better Orc value to Java value conversion in presence of overloaded methods, 3) cleanly dealing with Java's var arg signatures, 4) adding some memoization, 5) testing<commit_after>//
// JavaSiteForm.scala -- Scala object JavaSiteForm and class JavaClassProxy
// Project OrcScala
//
// $Id$
//
// Created by jthywiss on May 30, 2010.
//
// Copyright (c) 2010 The University of Texas at Austin. All rights reserved.
//
// Use and redistribution of this file is governed by the license terms in
// the LICENSE file found in the project's top-level directory and also found at
// URL: http://orc.csres.utexas.edu/license.shtml .
//

package orc.values.sites

import orc.TokenAPI
import orc.oil.nameless.Type // FIXME: Typechecker should operate on named types instead
import orc.values.Value
import orc.values.Literal
import orc.values.Signal
import orc.values.{Field => OrcField}
import orc.error.NotYetImplementedException
import orc.error.runtime.JavaException
import orc.error.runtime.ArityMismatchException
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.{Member => JavaMember}
import java.lang.reflect.{Constructor => JavaConstructor}
import java.lang.reflect.{Method => JavaMethod}
import java.lang.reflect.{Field => JavaField}
import java.lang.reflect.Modifier


/**
 * Services (such as name resolution) for use of plain old Java classes
 * as Orc sites.
 *
 * @author jthywiss
 */
object JavaSiteForm extends SiteForm {
  def resolve(name: String) = {
    new JavaClassProxy(loadClass(name))
  }
  private def loadClass(name:String) = getClass().getClassLoader().loadClass(name).asInstanceOf[Class[Object]] //TODO:FIXME: This should use the OrcAPI's loadClass, and the classpath from the OrcOptions
}


abstract class JavaProxy extends Site {

  def javaClass: Class[_]

  lazy val javaClassName: String = javaClass.getCanonicalName()

  def java2orc(javaValue: Object): Value = javaValue match {
    case v: Value => v
    case _: java.lang.Void => orc.values.Signal
    case null => Literal(null)
    case i: java.lang.Byte => Literal(BigInt(i.byteValue))
    case i: java.lang.Short => Literal(BigInt(i.shortValue))
    case i: java.lang.Integer => Literal(BigInt(i.intValue))
    case i: java.lang.Long => Literal(BigInt(i.longValue))
    case f: java.lang.Float => Literal(BigDecimal(f.floatValue))
    case f: java.lang.Double => Literal(BigDecimal(f.doubleValue))
    case s: java.lang.String => Literal(s)
    case b: java.lang.Boolean => Literal(b.booleanValue)
    case _ => new JavaObjectProxy(javaValue)
  }

  def orc2java(orcValue: Value): Object = orcValue match {
    case JavaObjectProxy(j) => j
    case Literal(v) => v.asInstanceOf[Object]
    case _ => orcValue
  }

  def hasMember(memberName: String): Boolean =
    //TODO: Memoize!  This is expensive!
    javaClass.getMethods().exists({_.getName().equals(memberName)}) ||
        javaClass.getFields().exists({_.getName().equals(memberName)})

  def invoke(theObject: Object, methodName: String, args: List[Value]): Value = {
    val method = try {
      chooseMethodForInvocation(methodName, args.map(orc2java(_)))
    } catch { // Fill in "blank" exceptions with more details
      case e: java.lang.NoSuchMethodError if (e.getMessage() == null) => throw new java.lang.NoSuchMethodError(classNameAndSignatureA(methodName, args.map(orc2java(_))))
      case e: java.lang.AbstractMethodError if (e.getMessage() == null) => throw new java.lang.AbstractMethodError(classNameAndSignatureA(methodName, args.map(orc2java(_))))
    }
    val convertedArgs = args.map(orc2java(_)).toArray //FIXME:TODO:convert Orc values to needed types for chosen method's formal params
//    println(javaClass.getCanonicalName())
//    println(method)
//    println(convertedArgs.getClass().getCanonicalName())
//    println(convertedArgs.length)
//    println(convertedArgs)
    java2orc(method.invoke(theObject, convertedArgs: _*))
  }

  private def classNameAndSignature(methodName: String, argTypes: List[Class[_]]): String = {
    javaClass.getCanonicalName()+"."+methodName+"("+argTypes.map(_.getCanonicalName()).mkString(", ")+")"
  }
  private def classNameAndSignatureA(methodName: String, args: List[Object]): String = {
    classNameAndSignature(methodName, args.map(_.getClass()))
  }
  
  def chooseMethodForInvocation(methodName: String, args: List[Object]): JavaMethod = {
    //Phase 0: Identify Potentially Applicable Methods
    //A member method is potentially applicable to a method invocation if and only if all of the following are true:
    //* The name of the member is identical to the name of the method in the method invocation.
    //* The member is accessible (§6.6) to the class or interface in which the method invocation appears.
    //* The arity of the member is lesser or equal to the arity of the method invocation.
    //* If the member is a variable arity method with arity n, the arity of the method invocation is greater or equal to n-1.
    //* If the member is a fixed arity method with arity n, the arity of the method invocation is equal to n.
    //* If the method invocation includes explicit type parameters, and the member is a generic method, then the number of actual type parameters is equal to the number of formal type parameters.
    val potentiallyApplicableMethods = javaClass.getMethods().filter({m => 
        m.getName().equals(methodName) &&
        Modifier.isPublic(m.getModifiers()) &&
        // Modfier ABSTRACT is handled later.
        (m.getParameterTypes().size == args.size ||
         m.isVarArgs() && m.getParameterTypes().size-1 <= args.size)})

    //Phase 1: Identify Matching Arity Methods Applicable by Subtyping
    val phase1Results = potentiallyApplicableMethods.filter( { m =>
      !m.isVarArgs() &&
      m.getParameterTypes().corresponds(args)({(fp, arg) => isApplicable(fp, arg, false)})
    } )

    if (phase1Results.nonEmpty) {
      return mostSpecificMethod(phase1Results.toList)
    }
    
    //Phase 2: Identify Matching Arity Methods Applicable by Method Invocation Conversion
    val phase2Results = potentiallyApplicableMethods.filter( { m =>
      !m.isVarArgs() &&
      m.getParameterTypes().corresponds(args)({(fp, arg) => isApplicable(fp, arg, true)})
    } )
    if (phase2Results.nonEmpty) {
      return mostSpecificMethod(phase2Results.toList)
    }

    //Phase 3: Identify Applicable Variable Arity Methods
    //FIXME:TODO: Implement var arg calls
    
    // No match
    throw new java.lang.NoSuchMethodError();
  }

  private def isApplicable(formalParamType: Class[_], actualArg: Object, allowConversion: Boolean): Boolean = {
    // allowConversion refers to method invocation conversion (JLS §5.3), which is one of:
    // 0. identity conversion
    // 1. widening primitive conversion (JLS §5.1.2) -- one of 19 conversions
    // 2. widening reference conversion (JLS §5.1.5) -- i.e., normal subtyping
    // 3. boxing conversion (one of 8) (JLS §5.1.7) optionally followed by widening reference conversion
    // 4. unboxing conversion (one of 8) (§5.1.8) optionally followed by a widening primitive conversion
    // "Method invocation conversions specifically do not include the implicit narrowing of integer constants which is part of assignment conversion"

    //TODO: Consider converting OrcList, etc. to java List, etc.
    val actualArgType = actualArg.getClass()
    if (!allowConversion || (!formalParamType.isPrimitive() && !actualArgType.isPrimitive())) {
      formalParamType.isInstance(actualArg)
    } else if (formalParamType.isPrimitive() && actualArgType.isPrimitive()) {
      isPrimWidenable(actualArgType, formalParamType)
    } else {
      if (formalParamType.isPrimitive()) { /* && actualArg is NOT primitive */
        val unboxedType = unbox(actualArgType)
        isPrimWidenable(unboxedType, formalParamType) || isOrcJavaNumConvertable(actualArgType, formalParamType)
      } else { /* formalParamType is NOT primitive && actualArg IS primitive */
        val boxedType = box(actualArgType)
        formalParamType.isAssignableFrom(boxedType)
      }
    }
  }

  private val booleanRefClass = classOf[java.lang.Boolean]
  private val byteRefClass = classOf[java.lang.Byte]
  private val charRefClass = classOf[java.lang.Character]
  private val shortRefClass = classOf[java.lang.Short]
  private val intRefClass = classOf[java.lang.Integer]
  private val longRefClass = classOf[java.lang.Long]
  private val floatRefClass = classOf[java.lang.Float]
  private val doubleRefClass = classOf[java.lang.Double]
  
  private val orcIntegralClass = classOf[java.math.BigInteger]
  private val orcFloatingPointClass = classOf[java.math.BigDecimal]

  private def box(primType: Class[_]): Class[_] = {
    primType match {
      case java.lang.Boolean.TYPE => booleanRefClass
      case java.lang.Byte.TYPE => byteRefClass
      case java.lang.Character.TYPE => charRefClass
      case java.lang.Short.TYPE => shortRefClass
      case java.lang.Integer.TYPE => intRefClass
      case java.lang.Long.TYPE => longRefClass
      case java.lang.Float.TYPE => floatRefClass
      case java.lang.Double.TYPE => doubleRefClass
      case _ => primType
    }
  }

  private def unbox(refType: Class[_]): Class[_] = {
    refType match {
      case `booleanRefClass` => java.lang.Boolean.TYPE
      case `byteRefClass` => java.lang.Byte.TYPE
      case `charRefClass` => java.lang.Character.TYPE
      case `shortRefClass` => java.lang.Short.TYPE
      case `intRefClass` => java.lang.Integer.TYPE
      case `longRefClass` => java.lang.Long.TYPE
      case `floatRefClass` => java.lang.Float.TYPE
      case `doubleRefClass` => java.lang.Double.TYPE
      case _ => refType
    }
  }

  /** "true" for an identity conversion (JLS §5.1.1) or a widening primitive conversion (JLS §5.1.2) */
  private def isPrimWidenable(fromPrimType: Class[_], toPrimType: Class[_]): Boolean = {
    (fromPrimType == toPrimType) || (fromPrimType match {
      case java.lang.Byte.TYPE => toPrimType == java.lang.Short.TYPE || toPrimType == java.lang.Integer.TYPE || toPrimType == java.lang.Long.TYPE || toPrimType == java.lang.Float.TYPE || toPrimType == java.lang.Double.TYPE
      case java.lang.Short.TYPE => toPrimType == java.lang.Integer.TYPE || toPrimType == java.lang.Long.TYPE || toPrimType == java.lang.Float.TYPE || toPrimType == java.lang.Double.TYPE
      case java.lang.Character.TYPE => toPrimType == java.lang.Integer.TYPE || toPrimType == java.lang.Long.TYPE || toPrimType == java.lang.Float.TYPE || toPrimType == java.lang.Double.TYPE
      case java.lang.Integer.TYPE => toPrimType == java.lang.Long.TYPE || toPrimType == java.lang.Float.TYPE || toPrimType == java.lang.Double.TYPE
      case java.lang.Long.TYPE => toPrimType == java.lang.Float.TYPE || toPrimType == java.lang.Double.TYPE 
      case java.lang.Float.TYPE => toPrimType == java.lang.Double.TYPE
      case _ => false
    })
  }

  private def isOrcJavaNumConvertable(fromType: Class[_], toType: Class[_]): Boolean = {
    toType match {
      case `byteRefClass` => fromType == orcIntegralClass || fromType == orcFloatingPointClass
      case `shortRefClass` => fromType == orcIntegralClass || fromType == orcFloatingPointClass
      case `intRefClass` => fromType == orcIntegralClass || fromType == orcFloatingPointClass
      case `longRefClass` => fromType == orcIntegralClass || fromType == orcFloatingPointClass
      case `floatRefClass` => fromType == orcFloatingPointClass
      case `doubleRefClass` => fromType == orcFloatingPointClass
      case java.lang.Byte.TYPE => fromType == orcIntegralClass || fromType == orcFloatingPointClass
      case java.lang.Short.TYPE => fromType == orcIntegralClass || fromType == orcFloatingPointClass
      case java.lang.Integer.TYPE => fromType == orcIntegralClass || fromType == orcFloatingPointClass
      case java.lang.Long.TYPE => fromType == orcIntegralClass || fromType == orcFloatingPointClass 
      case java.lang.Float.TYPE => fromType == orcFloatingPointClass
      case java.lang.Double.TYPE => fromType == orcFloatingPointClass
      case _ => false
    }
  }

  private def mostSpecificMethod(methods: List[JavaMethod]): JavaMethod = {
    //FIXME:TODO: Implement var arg calls
    val maximallySpecificMethods = 
      methods.foldLeft(List[JavaMethod]())({(prevMostSpecific: List[JavaMethod], nextMethod: JavaMethod) =>
        if (prevMostSpecific.isEmpty) {
          List(nextMethod)
        } else { 
          if (isEqOrMoreSpecific(nextMethod, prevMostSpecific.head)) {
            if (isEqOrMoreSpecific(prevMostSpecific.head, nextMethod)) { // equally specific, add to list
              prevMostSpecific :+ nextMethod
            } else { // nextMethod strictly more specific
              List(nextMethod)
            }
          } else { // nextMethod > prevMostSpecific
            prevMostSpecific
          }
        }
      })
    if (maximallySpecificMethods.length == 1) {
      return maximallySpecificMethods.head
    } else {
      val concreteMethods = maximallySpecificMethods.filter({m => !Modifier.isAbstract(m.getModifiers())})
      concreteMethods.length match {
        case 1 => return concreteMethods.head
        case 0 => throw new java.lang.NoSuchMethodError()
        case _ => throw new java.lang.AbstractMethodError()
      }
    }
  }
  
  /** left is more or equally specific than right */
  private def isEqOrMoreSpecific(left: JavaMethod, right: JavaMethod): Boolean = {
    //FIXME:TODO: Add primitive types, since isAssignableFrom is too stupid
    left.getParameterTypes().corresponds(right.getParameterTypes())({(l,r) => r.isAssignableFrom(l)})
  }
}


/**
 * Wrapper for a plain old Java class as an Orc site
 *
 * @author jthywiss
 */
case class JavaClassProxy(val javaClass: Class[Object]) extends JavaProxy {
  // Reminder: A java.lang.Class could be a regular class, an interface, an array, or a primitive type.  

  override lazy val name = javaClass.getName()

  override def orcType(argTypes: List[Type]) = null //TODO:FIXME: Implement this

  override def call(args: List[Value], callingToken: TokenAPI) {
    args match {
      case List(OrcField("?")) => throw new NotYetImplementedException("MatchProxy not implemented yet") //TODO:FIXME: Implement this -- publish(new MatchProxy(javaClass))
      case List(OrcField(memberName)) => callingToken.publish(new JavaStaticMemberProxy(javaClass, memberName))
      case _ => {
        (try {
          javaClass.newInstance() //FIXME:TODO: use ctorArgs
        } catch {
          case e: InvocationTargetException => throw new JavaException(e.getCause())
          case e => throw new JavaException(e)
        }) match {
          case v: Value => callingToken.publish(v)
          case o => callingToken.publish(new JavaObjectProxy(o))
        }
      }
    }
  }
}


/**
 * Wrapper for a plain old Java object.
 *
 * @author jthywiss
 */
case class JavaObjectProxy(val theObject: Object) extends JavaProxy {

  override lazy val name = theObject.getClass().getName()

  override def orcType(argTypes: List[Type]) = null //TODO:FIXME: Implement this

  override def javaClass = theObject.getClass()

  override def call(args: List[Value], callingToken: TokenAPI) {
    args match {
      case List(OrcField(memberName)) => callingToken.publish(new JavaMemberProxy(theObject, memberName))
      case _ => callingToken.publish(invoke(theObject, "apply", args))
    }
  }
}


/**
 * An Orc field lookup result from a Java object
 *
 * @author jthywiss
 */
case class JavaMemberProxy(val theObject: Object, val memberName: String) extends JavaProxy {
  // Could be a method or field.  We defer this decision until we are called.

  override lazy val name = this.getClass().getCanonicalName()+"("+javaClassName+"."+memberName+", "+theObject.toString()+")"

  override def orcType(argTypes: List[Type]) = null //TODO:FIXME: Implement this

  override def javaClass = theObject.getClass()

  def call(args: List[Value], callingToken: TokenAPI) {
    args match {
      case List(OrcField(submemberName)) => {
        val javaField = javaClass.getField(memberName)
        callingToken.publish(submemberName match {
          case "read" if !hasMember("read") => new JavaFieldDerefSite(theObject, javaField)
          case "write" if !hasMember("write") => new JavaFieldAssignSite(theObject, javaField)
          case _ => new JavaMemberProxy(javaField.get(theObject), submemberName)
        })
      }
      case _ => callingToken.publish(invoke(theObject, memberName, args))
    }
  }
}


/**
 * An Orc field lookup result from a Java class
 *
 * @author jthywiss
 */
class JavaStaticMemberProxy(declaringClass: Class[_], memberName: String) extends JavaMemberProxy(null, memberName) {

  override def orcType(argTypes: List[Type]) = null //TODO:FIXME: Implement this

  override def javaClass = declaringClass

  override lazy val name = this.getClass().getCanonicalName()+"("+javaClassName+"."+memberName+")"

}


/**
 * A site that will dereference a Java object's field when called
 *
 * @author jthywiss
 */
case class JavaFieldDerefSite(val theObject: Object, val javaField: JavaField) extends JavaProxy {

  override def orcType(argTypes: List[Type]) = null //TODO:FIXME: Implement this

  override def javaClass = javaField.getDeclaringClass()

  override lazy val name = this.getClass().getCanonicalName()+"("+javaClassName+"."+javaField.getName()+", "+theObject+")"

  def call(args: List[Value], callingToken: TokenAPI) {
    args match {
      case List() => callingToken.publish(java2orc(javaField.get(theObject)))
      case _ => throw new ArityMismatchException(0, args.size)
    }
  }

}


/**
 * A site that will assign a value to a Java object's field when called
 *
 * @author jthywiss
 */
case class JavaFieldAssignSite(val theObject: Object, val javaField: JavaField) extends JavaProxy {

  override def orcType(argTypes: List[Type]) = null //TODO:FIXME: Implement this

  override def javaClass = javaField.getDeclaringClass()

  override lazy val name = this.getClass().getCanonicalName()+"("+javaClassName+"."+javaField.getName()+", "+theObject+")"

  def call(args: List[Value], callingToken: TokenAPI) {
    args match {
      case List(a) => {
        javaField.set(theObject, orc2java(a))
        callingToken.publish(Signal)
      }
      case _ => throw new ArityMismatchException(1, args.size)
    }
  }

}


//class JavaMethodProxy(val theObject: Object, val theMethod: JavaMethod) extends JavaMemberProxy {
//  override lazy val name = this.getClass().getCanonicalName()+"("+javaClassName+"."+theMethod.getName()+theMethod.getParameterTypes().map(_.getDeclaringClass().getCanonicalName())mkString("(", ",", ")")+", "+theObject.toString()+")"
//}
//
//
//class JavaConstructorProxy(val theConstructor: JavaConstructor[_]) extends JavaMemberProxy {
//  // Yes, c'tors are not "members" of a class per the JLS/JVMS, but we don't care to make the distinction
//  override lazy val name = this.getClass().getCanonicalName()+"("+javaClassName+theConstructor.getParameterTypes().map(_.getDeclaringClass().getCanonicalName())mkString("(", ",", ")")+")"
//}
{'fraction_non_alphanumeric': 0.11710144927536231, 'fraction_numerical': 0.003961352657004831, 'mean_word_length': 3.7490250057352603, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4932038', 'n_tokens_mistral': 6231, 'n_tokens_neox': 6070, 'n_words': 1685}