aboutsummaryrefslogtreecommitdiff
path: root/macros/src/main/scala/ch/jodersky/jni/annotations.scala
blob: c1caa48c22ce7d6755b3dbb248b17ed1a5170cd5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package ch.jodersky.jni

import macrocompat.bundle
import util.PlatformMacros

import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly

@bundle
class nativeLoaderMacro(val c: Context) {

  def impl(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    val nativeLibrary: String = c.prefix.tree match {
      case Apply(_, List(Literal(Constant(x: String)))) => x
      case Apply(_, xs :: tail) => c.abort(xs.pos, "Native library must be a constant.")
      case t => c.abort(t.pos, "Native library not specified.")
    }

    def inject(annottees: List[Tree]): List[Tree] = annottees match {

      case ClassDef(mods, name, tparams, Template(parents, self, body)) :: tail =>
        val extra = q"""
        {
          ${name.toTermName}
        }
        """

        val module: List[Tree] = tail match {
          case Nil => inject(List(q"""object ${name.toTermName}"""))
          case other => inject(other)
        }

        ClassDef(mods, name, tparams, Template(parents, self, body :+ extra)) :: module

      //q"$mods object $name extends ..$parents {$self => ..$body }" :: Nil =>
      case ModuleDef(mods, name, Template(parents, self, body)) :: Nil =>
        val extra = q"""{
          def loadPackaged(): Unit = {
            import java.io.File
            import java.nio.file.{Files, Path}

            val lib: String = System.mapLibraryName($nativeLibrary)

            val tmp: Path = Files.createTempDirectory("jni-")
            val plat: String = ${PlatformMacros.current(c)}

            val resourcePath: String = "/native/" + plat + "/" + lib
            val resourceStream = Option($name.getClass.getResourceAsStream(resourcePath)) match {
              case Some(s) => s
              case None => throw new UnsatisfiedLinkError(
                "Native library " + lib + " (" + resourcePath + ") cannot be found on the classpath.")
            }

            val extractedPath = tmp.resolve(lib)

            try {
              Files.copy(resourceStream, extractedPath)
            } catch {
              case ex: Exception => throw new UnsatisfiedLinkError(
                "Error while extracting native library: " + ex)
            }

            System.load(extractedPath.toAbsolutePath.toString)
          }

          def load(): Unit = try {
            System.loadLibrary($nativeLibrary)
          } catch {
            case ex: UnsatisfiedLinkError => loadPackaged()
          }

          load()
          }"""

        ModuleDef(mods, name, Template(parents, self, body :+ extra)) :: Nil

      case _ =>
        c.abort(c.enclosingPosition, "nativeLoader can only be annotated to classes and singleton objects")

    }

    val outputs = inject(annottees.map(_.tree).toList)

    val result: Tree = Block(outputs, Literal(Constant(())))

    c.Expr[Any](result)
  }

}

@compileTimeOnly("Macro Paradise must be enabled to apply annotation.")
class nativeLoader(nativeLibrary: String) extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro nativeLoaderMacro.impl
}