All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalan.MethodCalls.scala Maven / Gradle / Ivy

The newest version!
package scalan

import scala.annotation.{tailrec, unused}
import scala.reflect.ClassTag
import debox.{Buffer => DBuffer}
import debox.cfor
import sigma.reflection.RMethod
import sigma.util.CollectionUtil.TraversableOps

trait MethodCalls extends Base { self: Scalan =>

  def delayInvoke = throw new DelayInvokeException

  /** Graph node to represent invocation of the method of some class.
    * @param receiver    node ref representing instance on which the method is called
    * @param method      method which is called (descriptor from `sigma.reflection`)
    * @param args        node refs representing arguments passed to the method
    * @param neverInvoke it true this method cannot be performed, even if the
    *                    receiver node allow this
    * @param resultType  type descriptor of the method's result
    * @param isAdapterCall whether this MC was created by generated adapter class.
    *                      This typically means, that receiver node doesn't implement
    *                      given `method`.
    */
  case class MethodCall private[MethodCalls](receiver: Sym, method: RMethod, args: Seq[AnyRef], neverInvoke: Boolean)
                                            (val resultType: Elem[Any], val isAdapterCall: Boolean = false) extends Def[Any] {

    override def mirror(t: Transformer): Ref[Any] = {
      val len = args.length
      val args1 = new Array[AnyRef](len)
      cfor(0)(_ < len, _ + 1) { i =>
        args1(i) = transformProductParam(args(i), t).asInstanceOf[AnyRef]
      }
      val receiver1 = t(receiver)
      // in the case neverInvoke is false, the method is invoked in rewriteDef
      mkMethodCall(receiver1, method, args1, neverInvoke, isAdapterCall, resultType).asInstanceOf[Ref[Any]]
    }

    override def toString = {
      val methodStr = method.toString.replace("java.lang.", "").
        replace("public ", "").replace("abstract ", "")
      s"MethodCall($receiver, $methodStr, [${args.mkString(", ")}], $neverInvoke)"
    }

    /** Try invoke `method` on the node instance refered by `receiver`.
      * Each MC node contains enough information to perform invocation using
      * `sigma.reflection.RMethod.invoke` method. However, this is not possible
      * if the node pointed to by `receiver` don't implement this method,
      * for example when receiver is Lambda variable pointing to Variable node
      * instance (in which case this MC was created by adapter)
      * @return invocation result descriptor.
      * @see InvokeResult
      */
    def tryInvoke: InvokeResult =
      if (neverInvoke && !isAdapterCall) {
        InvokeImpossible
      } else {
        invokeMethod[InvokeResult](
          receiver, method, args.toArray,
          { res => InvokeSuccess(res.asInstanceOf[Sym]) },
          { InvokeFailure(_) },
          { InvokeImpossible }
        )
      }

    override def equals(other: Any): Boolean = (this eq other.asInstanceOf[AnyRef]) || {
      other match {
        case other: MethodCall =>
          receiver == other.receiver &&
          method == other.method &&
          resultType.name == other.resultType.name &&
          neverInvoke == other.neverInvoke &&
          isAdapterCall == other.isAdapterCall &&
          args.length == other.args.length &&
          args.sameElementsNested(other.args) // this is required in case method have T* arguments
        case _ => false
      }
    }

    override lazy val hashCode: Int = {
      var h = receiver.hashCode() * 31 + method.hashCode()
      h = h * 31 + resultType.name.hashCode
      h = h * 31 + (if(neverInvoke) 1 else 0)
      h = h * 31 + (if(isAdapterCall) 1 else 0)
      h = h * 31 + args.hashCode()
      h
    }
  }

  /** Represents invocation of constructor of the class described by `eA`.
    * @param  eA          class descriptor for new instance
    * @param  args        arguments of class constructor
    */
  case class NewObject[A](eA: Elem[A], args: Seq[Any]) extends BaseDef[A]()(eA) {
    override def transform(t: Transformer) = NewObject(eA, t(args))
  }

  /** Creates new MethodCall node and returns its node ref. */
  def mkMethodCall(receiver: Sym, method: RMethod, args: Seq[AnyRef],
                   neverInvoke: Boolean, isAdapterCall: Boolean, resultElem: Elem[_]): Sym = {
    reifyObject(MethodCall(receiver, method, args, neverInvoke)(asElem[Any](resultElem), isAdapterCall))
  }

  /** Creates new NewObject node and returns its node ref. */
  def newObjEx[A](args: Any*)(implicit eA: Elem[A]): Ref[A] = {
    reifyObject(NewObject[A](eA, args))
  }

  @tailrec
  private def baseCause(e: Throwable): Throwable = e match {
    case e: ExceptionInInitializerError => baseCause(e.getCause)
    case e => e
  }

  /** This method is called for each MethodCall node which is about to be added to the graph.
    * This means `mc` has been examined by all the rewrite rules, but has not need rewritten.
    * Now, if this method returns null, then mc will be added to the graph.
    * However, in this method, `mc` can be examined by a second set of RW rules
    * (kind of lower priority rules). These rules kind of context dependent, because at this
    * point we know that the first RW set didn't triggered any rewrite. */
  def rewriteNonInvokableMethodCall(@unused mc: MethodCall): Ref[_] = null

  /** Create delegate instance suitable for method invocation.
    * It is used when T is a class or a trait and the node referred by x doesn't conform to T.
    * This method returns dynamically constructed instance, which conforms to T.
    * Whenever a method of T is called on that instance, the call is intercepted and
    * `DelegatedInterceptionHandler.invoke` method is called, then a new MethodCall can
    * be constructed (which is befavior by default).
    */
  protected def unrefDelegate[T <: AnyRef](x: Ref[T])(implicit ct: ClassTag[T]): T = {
    val d = x.node
    if (d.isInstanceOf[Const[_]])
      d.asInstanceOf[Const[T]@unchecked].x
    else
      !!!(s"Cannot do undefDelegate($x -> ${x.node})")
  }

  /** Generic helper to call the given method on the given receiver node. */
  private[scalan] def invokeMethod[A](receiver: Sym, m: RMethod, args: Array[AnyRef],
                              onInvokeSuccess: Any => A,
                              onInvokeException: Throwable => A,
                              onInvokeImpossible: => A): A = {
    val d = receiver.node
    if (canBeInvoked(d, m, args)) {
      try {
        val res = m.invoke(d, args:_*)
        onInvokeSuccess(res)
      } catch {
        case e: Exception => onInvokeException(baseCause(e))
      }
    }
    else
      onInvokeImpossible
  }

  /** Method invocation enabler.
    * @return  true if the given method can be invoked on the given node. */
  def isInvokeEnabled(d: Def[_], m: RMethod) = true

  /** Method invocation checker. */
  protected def canBeInvoked(d: Def[_], m: RMethod, args: Array[AnyRef]) = {
    m.getDeclaringClass.isAssignableFrom(d.getClass) && isInvokeEnabled(d, m)
  }

  /** Result of MethodCall invocation.
    * @see tryInvoke */
  sealed abstract class InvokeResult
  /** Successful MethodCall invocation with the given result. */
  case class InvokeSuccess(result: Ref[_]) extends InvokeResult
  /** Exception thrown during MethodCall invocation. */
  case class InvokeFailure(exception: Throwable) extends InvokeResult
  /** Invocation is not possible, e.g. when receiver doesn't implemented the method. */
  case object InvokeImpossible extends InvokeResult

  def throwInvocationException(whatFailed: String, cause: Throwable, receiver: Sym, m: RMethod, args: Seq[Any]) = {
    val buf = DBuffer.empty[Sym]
    buf += receiver
    Def.extractSyms(args, buf)
    val deps = buf.toArray()
    !!!(s"$whatFailed (${receiver.varNameWithType}).${m.getName}(${args.mkString(", ")}) failed", baseCause(cause), deps: _*)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy