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

iota.IdMacros.scala Maven / Gradle / Ivy

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

import java.io.{FileOutputStream, OutputStreamWriter}

import android.view.View

import scala.annotation.implicitNotFound
import scala.reflect.ClassTag
import scala.reflect.macros.Context

@implicitNotFound("cannot use findView prior to source application of id(Int)")
case class ViewIdType[+A : ClassTag]()
/**
  * @author pfnguyen
  */
private[iota] trait IdMacros {
  implicit def materializeIdType: ViewIdType[Any] = macro IdMacros.matIdType
}
private[iota] object IdMacros {
  def matIdType(c: Context): c.Expr[ViewIdType[Any]] = {
    import FileUtil._
    import c.universe._

    val base = target(c.enclosingUnit.source.file.file)
    val strFile = file(base, STR_TYPE_FILE)
    val intFile = file(base, INT_TYPE_FILE)

    val (strs, ints) = loadMappings(strFile, intFile)

    // check `Product` to be source compatible with scala 2.10 and 2.11
    val mapped = c.enclosingImplicits.headOption.fold {
      c.abort(c.enclosingPosition, "materializeIdType is not being used properly for implicit resolution")
    } { impls =>
      val tree = impls match {
        case p: Product if p.productArity == 2 => p.productElement(1).asInstanceOf[c.Tree]
        case p: Product if p.productArity == 4 => p.productElement(3).asInstanceOf[c.Tree]
      }
      val idInfo: Either[String,Int] = tree.collect {
        case Apply(_, y :: _) => y
      }.head match {
        case Literal(Constant(n: Int)) => Right(n)
        case t => Left(t.symbol.fullName)
      }

      idInfo.left.map(strs.get).right.map(ints.get).fold(identity,identity) getOrElse
        c.abort(c.enclosingPosition,
          "Id used before declaring in id(_), cannot determine type, aborting")
    }

    val tpe = rootMirror.staticClass(mapped).asType.toType
    c.Expr[ViewIdType[Any]](Apply(
      Apply(
        TypeApply(
          Select(reify(ViewIdType).tree, newTermName("apply")),
          List(TypeTree(tpe))
        ),
        List()
      ),
      List(Select(reify(scala.Predef).tree, newTermName("implicitly")))
    ))
  }

  private[this] val INT_TYPE_FILE = "iota-int-ids.txt"
  private[this] val STR_TYPE_FILE = "iota-str-ids.txt"
  private[this] var strMap = Map.empty[String,String]
  private[this] var intMap = Map.empty[Int,String]
  private[this] var intMapTime = 0l
  private[this] var strMapTime = 0l

  def tIdImpl[A <: View : c.WeakTypeTag](c: Context)(id: c.Expr[Int]): c.Expr[Kestrel[A]] = {
    import FileUtil._
    import c.universe._
    val tpeInfo = c.weakTypeOf[A].typeSymbol.fullName
    if (tpeInfo == "iota.ViewCombinators.A")
      c.abort(c.enclosingPosition, "Unable to determine concrete type for A")
    val idInfo: Either[String,Int] = id match {
      case c.Expr(Literal(Constant(n: Int))) => Right(n)
      case x => Left(x.tree.symbol.fullName)
    }

    val base = target(c.enclosingUnit.source.file.file)
    base.mkdirs()
    val strFile = file(base, STR_TYPE_FILE)
    val intFile = file(base, INT_TYPE_FILE)
    val (strs, ints) = loadMappings(strFile, intFile)
    val newMappingFile = idInfo.fold(_ => strFile, _ => intFile)
    def addMapping[M](mapping: Map[M,String], id: M): Unit = {
      val item = mapping.get(id)
      item match {
        case Some(tpe) =>
          if (tpe != tpeInfo) {
            // reassigning types will cause the mapping file to grow without bound
            // when building incrementally  :-( fix how?
            val tpe1 = rootMirror.staticClass(tpe).baseClasses.collect { case c: ClassSymbol if !c.isTrait => c }.reverse
            val tpe2 = rootMirror.staticClass(tpeInfo).baseClasses.collect { case c: ClassSymbol if !c.isTrait => c }.reverse
            val common = (tpe1 zip tpe2).takeWhile(t => t._1 == t._2).lastOption map (_._1.fullName) getOrElse "android.view.View"
            c.warning(c.enclosingPosition, s"type reassigned, $tpe => $tpeInfo using $common")
            writeNewMapping(newMappingFile, common, idInfo)
          }
        case None =>
          writeNewMapping(newMappingFile, tpeInfo, idInfo)
      }
    }
    idInfo.right.foreach { addMapping(ints, _) }
    idInfo.left.foreach { addMapping(strs, _) }

    reify {
      kestrel ((v: A) => v.setId(id.splice))
    }
  }

  private[this] def writeNewMapping(f: java.io.File, tpe: String, idInfo: Either[String,Int]): Unit = {
    val fout = new OutputStreamWriter(new FileOutputStream(f, true), "utf-8")
    fout.write(s"$tpe ${idInfo.fold(identity,identity)}\n")
    fout.close()
  }

  private[this] def fileToMap[A,B](f: java.io.File, fn: (String => (A,B))): Map[A,B] = {
    if (f.isFile) {
      val src = io.Source.fromFile(f)
      try {
        src.getLines.map(fn).toMap
      } finally {
        src.close()
      }
    } else {
      Map.empty[A,B]
    }
  }
  private[this] def loadMappings(strfile: java.io.File, intfile: java.io.File): (Map[String,String],Map[Int,String]) = {
    if (intfile.lastModified > intMapTime) {
      intMap = fileToMap(intfile, { s =>
        val parts = s.split(' ')
        (parts(1).toInt, parts(0))
      })
      intMapTime = intfile.lastModified
    }
    if (strfile.lastModified > strMapTime) {
      strMap = fileToMap(strfile, { s =>
        val parts = s.split(' ')
        (parts(1), parts(0))
      })
      strMapTime = strfile.lastModified
    }
    (strMap, intMap)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy