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

iota.HookMacro.scala Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC3
Show newest version
package iota

import scala.reflect.macros.Context

/**
  * @author pfnguyen
  */
private[iota] object HookMacro {
  def applyHook0[V: c.WeakTypeTag](c: Context)(event: c.Expr[String])(handler: c.Expr[IO[Any]]): c.Expr[Kestrel[V]] = {
    val helper = new HookMacro[c.type](c)
    helper.applyHook0(event)(handler)
  }

  def applyHookM0[V: c.WeakTypeTag](c: Context)(event: c.Expr[String])(handler: c.Expr[IO[Any]]): c.Expr[Kestrel[V]] = {
    val helper = new HookMacro[c.type](c)
    helper.applyHookM0(event)(handler)
  }

  def applyHook[V: c.WeakTypeTag](c: Context)(event: c.Expr[String])(handler: c.Expr[Any]): c.Expr[Kestrel[V]] = {
    val helper = new HookMacro[c.type](c)
    helper.applyHook(event)(handler)
  }

  def applyHookM[V: c.WeakTypeTag](c: Context)(event: c.Expr[String])(handler: c.Expr[Any]): c.Expr[Kestrel[V]] = {
    val helper = new HookMacro[c.type](c)
    helper.applyHookM(event)(handler)
  }

  private[iota] class HookMacro[C <: Context](val c: C) extends ListenerMacros[C] {
    private val OBJECT_FUNCTIONS = Set("clone", "toString", "hashCode", "equals", "finalize")

    import c.universe._

    def callbackPartsFor[V: c.WeakTypeTag](e: String, methodName: Option[String] = None) = {
      val tp = weakTypeOf[V]
      val setter = util.Try {
        tp.member(newTermName(s"setOn${e.capitalize}Listener")).asMethod
      }.recover { case _ =>
        tp.member(newTermName(s"addOn${e.capitalize}Listener")).asMethod
      }.recover { case _ =>
        tp.member(newTermName(s"set${e.capitalize}Listener")).asMethod
      }.recover { case _ =>
        tp.member(newTermName(s"add${e.capitalize}Listener")).asMethod
      }.recover { case _ =>
        tp.member(newTermName(s"set${e.capitalize}")).asMethod
      }.recover { case _ =>
        tp.member(newTermName(e)).asMethod
      }.getOrElse(c.abort(c.enclosingPosition, s"no event listener for $e found in $tp"))
      val listener = setter.paramss.head.head.typeSignature

      val onMethod = methodName getOrElse e
      val on = util.Try {
        listener.member(newTermName(onMethod)).asMethod
      }.recover { case _ =>
        listener.member(newTermName("on" + onMethod.capitalize)).asMethod
      } getOrElse {
        c.abort(c.enclosingPosition, s"Unsupported event listener class in $setter (no method '$onMethod' in $listener)")
      }
      // hack for partially implemented listeners, until we move to 2.11 only and can use isAbstract
      val toImplement = if (!listener.typeSymbol.asClass.isAbstractClass) Nil
      else listener.members.collect {
        case m: MethodSymbol if !m.isFinal && m.isJava && !m.isConstructor && !OBJECT_FUNCTIONS(m.name.encoded) && m.name.encoded != on.name.encoded =>
          m
      }

      (setter, listener, on, toImplement.toList)
    }

    def applyHook0[V: c.WeakTypeTag](event: c.Expr[String])(handler: c.Expr[IO[Any]]): c.Expr[Kestrel[V]] = {
      val Expr(Literal(Constant(e: String))) = event
      val (setter, listener, on, toOverride) = callbackPartsFor(e)
      val anon = newListenerClass(listener, on, handler.tree, toOverride, false)
      c.Expr[Kestrel[V]](newKestrel(weakTypeOf[V], setter.name.encoded, anon))
    }

    def applyHookM0[V: c.WeakTypeTag](event: c.Expr[String])(handler: c.Expr[IO[Any]]): c.Expr[Kestrel[V]] = {
      val Literal(Constant(outer: String)) = c.prefix.tree.collect { case Apply(n, xs) => xs }.head.last
      val Expr(Literal(Constant(e: String))) = event
      val (setter, listener, on, toOverride) = callbackPartsFor(outer, Option(e))

      val anon = newListenerClass(listener, on, handler.tree, toOverride)
      c.Expr[Kestrel[V]](newKestrel(weakTypeOf[V], setter.name.encoded, anon))
    }

    def applyHook[V: c.WeakTypeTag](event: c.Expr[String])(handler: c.Expr[Any]): c.Expr[Kestrel[V]] = {
      val Expr(Literal(Constant(e: String))) = event
      val (setter, listener, on, toOverride) = callbackPartsFor(e)
      val anon = newListenerClass(listener, on, handler.tree, toOverride, true)
      c.Expr[Kestrel[V]](newKestrel(weakTypeOf[V], setter.name.encoded, anon))
    }

    def applyHookM[V: c.WeakTypeTag](event: c.Expr[String])(handler: c.Expr[Any]): c.Expr[Kestrel[V]] = {
      val Literal(Constant(outer: String)) = c.prefix.tree.collect { case Apply(n, xs) => xs }.head.last
      val Expr(Literal(Constant(e: String))) = event
      val (setter, listener, on, toOverride) = callbackPartsFor(outer, Option(e))

      val anon = newListenerClass(listener, on, handler.tree, toOverride, true)
      c.Expr[Kestrel[V]](newKestrel(weakTypeOf[V], setter.name.encoded, anon))
    }

    def newKestrel(tpe: Type, method: String, handler: Tree) = {
      val vparam = c.fresh()
      Apply(
        Select(Ident(newTermName("iota")), newTermName("kestrel")),
        List(
          Function(
            List(ValDef(Modifiers(Flag.PARAM), newTermName(vparam), TypeTree(tpe), EmptyTree)),
            Apply(Select(Ident(newTermName(vparam)), newTermName(method)), List(handler))
          )
        )
      )
    }
  }
}

private[iota] trait ListenerMacros[C <: Context] extends Internal210 {
  val c: C
  import c.universe._

  def newListenerClass(tpe: Type, sym: MethodSymbol, handler: Tree, overrides: List[MethodSymbol], handleArgs: Boolean = false, handleIO: Boolean = true) = {
    val listenerTypeName = c.fresh("$anon")
    Block(
      List(
        ClassDef(
          Modifiers(Flag.FINAL), newTypeName(listenerTypeName), List(),
          Template(
            List(TypeTree(tpe)), emptyValDef,
            List(
              DefDef(
                Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(),
                Block(
                  List(
                    Apply(
                      Select(
                        Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR
                      ), List()
                    )
                  ), Literal(Constant(()))
                )
              ), newListenerMethod(sym, handler, handleArgs, handleIO)
            ) ++ newListenerOverrides(overrides)
          )

        )
      ),
      Apply(Select(New(Ident(newTypeName(listenerTypeName))), nme.CONSTRUCTOR), List())
    )
  }

  // semi-copied from TreeGen
  def mkZero(tp: Type): Tree = Literal(mkConstantZero(tp))
  def mkConstantZero(tp: Type): Constant =
    if (tp =:= typeOf[Unit])         Constant(())
    else if (tp =:= typeOf[Boolean]) Constant(false)
    else if (tp =:= typeOf[Float])   Constant(0.0f)
    else if (tp =:= typeOf[Float])   Constant(0.0d)
    else if (tp =:= typeOf[Byte])    Constant(0.toByte)
    else if (tp =:= typeOf[Short])   Constant(0.toShort)
    else if (tp =:= typeOf[Int])     Constant(0)
    else if (tp =:= typeOf[Long])    Constant(0L)
    else if (tp =:= typeOf[Char])    Constant(0.toChar)
    else                             Constant(null)

  def newListenerOverrides(overrides: List[MethodSymbol]): List[c.Tree] = {
    overrides map { sym =>
      val ret = mkZero(sym.returnType)
      val params = sym.paramss.head map { p =>
        ValDef(Modifiers(Flag.PARAM),
          newTermName(c.fresh()),
          TypeTree(p.typeSignature), EmptyTree)
      }
      DefDef(
        Modifiers(Flag.OVERRIDE),
        newTermName(sym.name.encoded),
        List(),
        List(params),
        TypeTree(),
        ret
      )
    }
  }
  def newListenerMethod(sym: MethodSymbol, handler: Tree, handleArgs: Boolean = false, withIO: Boolean = true) = {
    val params = sym.paramss.head map { p =>
      ValDef(Modifiers(Flag.PARAM),
        newTermName(c.fresh()),
        TypeTree(p.typeSignature), EmptyTree)
    }

    val h = spliceTree(c)(c.internal.enclosingOwner, handler)

    val handle = if (handleArgs) {
      Apply(Select(h, newTermName("apply")), params.map(p => Ident(p.name)))
    } else h

    val handleIO = if (withIO) {
      Apply(Select(handle, newTermName("perform")), Nil)
    } else handle

    DefDef(
      Modifiers(Flag.OVERRIDE),
      newTermName(sym.name.encoded),
      List(),
      List(params),
      TypeTree(),
      handleIO
    )
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy