summaryrefslogblamecommitdiff
path: root/core/src/main/scala/forge/discover/Discovered.scala
blob: 3b08f7f700890ef2ce1a6b1b189d0e52729a2a3e (plain) (tree)
1
2
3
4
5
6
7
8
9


                          
                                                 

                                

                                            
 









                                                                               




                                                                                            


                                                                                    

                   

                                                        
                                               



                                           






                                                                                         
                                                                                                       
            

   




                                                                        
                                                                                                
 







                                                                                               
                                                                 

                                                                                                                      
                                         
               



                                                                                           
 
                                                                      

                                 
       
 







                                                    



                                                                  
                                                                   
                                    
       
 
                                                                                

     










                                                                                           


                                           
                        

        

   
package forge.discover

import forge.define.Target
import forge.discover.Router.{EntryPoint, Result}
import play.api.libs.json.Format

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

class Discovered[T](val targets: Seq[(Seq[String], Format[_], T => Target[_])],
                    val mains: Seq[NestedEntry[T, _]]){
  def apply(t: T) = targets.map{case (a, f, b) => (a, f, b(t)) }
}

case class Labelled[T](target: Target[T],
                       format: Format[T],
                       segments: Seq[String])


case class NestedEntry[T, V](path: Seq[String], resolve: T => V, entryPoint: EntryPoint[V]){
  def invoke(target: T, groupedArgs: Seq[(String, Option[String])]): Result[Target[Any]] = {
    entryPoint.invoke(resolve(target),groupedArgs)
  }
}
object NestedEntry{
  def make[T, V](path: Seq[String], resolve: T => V)
                (entryPoint: EntryPoint[V]) = NestedEntry(path, resolve, entryPoint)
}
object Discovered {
  def consistencyCheck[T](base: T, d: Discovered[T]) = {
    val inconsistent = for{
      (path, formatter, targetGen) <- d.targets
      if targetGen(base) ne targetGen(base)
    } yield path
    inconsistent
  }
  def makeTuple[T, V](path: Seq[String], func: T => Target[V])(implicit f: Format[V]) = {
    (path, f, func)
  }


  def mapping[T: Discovered](t: T): Map[Target[_], Labelled[_]] = {
    implicitly[Discovered[T]].apply(t)
      .map(x => x._3 -> Labelled(x._3.asInstanceOf[Target[Any]], x._2.asInstanceOf[Format[Any]], x._1))
      .toMap
  }

  implicit def apply[T]: Discovered[T] = macro applyImpl[T]

  def applyImpl[T: c.WeakTypeTag](c: Context): c.Expr[Discovered[T]] = {
    import c.universe._
    val tpe = c.weakTypeTag[T].tpe
    def rec(segments: List[String], t: c.Type): (Seq[(Seq[String], Tree)], Seq[Seq[String]]) = {

      val r = new Router(c)
      val selfMains =
        for(tree <- r.getAllRoutesForClass(t.asInstanceOf[r.c.Type]).asInstanceOf[Seq[c.Tree]])
        yield (segments, tree)

      val items = for {
        m <- t.members.toSeq
        if
          (m.isTerm && (m.asTerm.isGetter || m.asTerm.isLazy)) ||
          m.isModule ||
          (m.isMethod && m.typeSignature.paramLists.isEmpty && m.typeSignature.resultType <:< c.weakTypeOf[Target[_]])
        if !m.name.toString.contains('$')
      } yield {
        val extendedSegments = m.name.toString :: segments
        val self =
          if (m.typeSignature.resultType <:< c.weakTypeOf[Target[_]]) Seq(extendedSegments)
          else Nil

        val (mains, children) = rec(extendedSegments, m.typeSignature)

        (mains, self ++ children)
      }

      val (mains, targets) = items.unzip
      Tuple2(
        selfMains ++ mains.flatten,
        targets.flatten
      )
    }

    val (entryPoints, reversedPaths) = rec(Nil, tpe)

    val result = for(reversedPath <- reversedPaths.toList) yield {
      val base = q"${TermName(c.freshName())}"
      val segments = reversedPath.reverse.toList
      val ident = segments.foldLeft[Tree](base) { (prefix, name) =>
        q"$prefix.${TermName(name)}"
      }

      q"forge.discover.Discovered.makeTuple($segments, ($base: $tpe) => $ident)"
    }

    val nested = for{
      (reversedSegments, entry) <- entryPoints
    } yield {
      val segments = reversedSegments.reverse
      val arg = TermName(c.freshName())
      val select = segments.foldLeft[Tree](Ident(arg)) { (prefix, name) =>
        q"$prefix.${TermName(name)}"
      }
      q"forge.discover.NestedEntry.make(Seq(..$segments), ($arg: $tpe) => $select)($entry)"
    }

    c.Expr[Discovered[T]](q"""
      new _root_.forge.discover.Discovered(
        $result,
        ${nested.toList}
      )
    """)
  }
}