Skip to content

Commit

Permalink
Dynamic loading works.
Browse files Browse the repository at this point in the history
Next steps are to provide a better interface to generated classes than
Java reflection. Scala Dynamic trait should prove useful.
  • Loading branch information
psuter committed Dec 3, 2012
1 parent 4ccfc7e commit 70072dd
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 27 deletions.
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ scalacOptions += "-deprecation"

scalacOptions += "-unchecked"

scalacOptions += "-Xexperimental"

libraryDependencies += "org.scalatest" %% "scalatest" % "1.8"

26 changes: 26 additions & 0 deletions src/main/scala/cafebabe/CafebabeClassLoader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cafebabe

import scala.collection.mutable.{Map=>MutableMap}

/** A `ClassLoader` with the capability for loading cafebabe
* `ClassFile`s directly from memory. */
class CafebabeClassLoader extends ClassLoader {
private val classes : MutableMap[String,Array[Byte]] = MutableMap.empty

def register(classFile : ClassFile) {
val byteStream = (new ByteStream) << classFile
classes(classFile.className) = byteStream.getBytes
}

override def findClass(name : String) : Class[_] = {
classes.get(name) match {
case Some(ba) => defineClass(name, ba, 0, ba.length)
case None => super.findClass(name)
}
}

def newInstance(name : String) : AnyRef = {
val klass = this.loadClass(name)
klass.newInstance().asInstanceOf[AnyRef]
}
}
9 changes: 3 additions & 6 deletions src/main/scala/cafebabe/ClassFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,15 @@ class ClassFile(val className: String, parentName: Option[String] = None) extend
}

/** Writes the binary representation of this class file to a file. */
def writeToFile(fileName : String) : Unit = {
def writeToFile(fileName : String) {
// The stream we'll ultimately use to write the class file data
val byteStream = new ByteStream
byteStream << this
byteStream.writeToFile(fileName)
}

/** Loads the class using the current class loader. */
def dynamicallyLoad : Unit = {
val byteStream = (new ByteStream) << this
val bytes : Array[Byte] = byteStream.getBytes
CustomClassLoader.registerClass(className, bytes)
def registerWithClassLoader(classLoader : CafebabeClassLoader) {
classLoader.register(this)
}

def toStream(byteStream: ByteStream): ByteStream = {
Expand Down
18 changes: 0 additions & 18 deletions src/main/scala/cafebabe/CustomClassLoader.scala

This file was deleted.

13 changes: 13 additions & 0 deletions src/main/scala/cafebabe/DynObj.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cafebabe

/** Do not rely on this object, as it will change/be renamed, etc.
* The goal is for `CafebabeClassLoader`s to generate `DynObj`s or
* equivalent, so that users of dynamic class generation have to
* write explicit reflection code as little as possible. */
class DynObj private[cafebabe](base : AnyRef) extends Dynamic {
// getDeclaredMethods to build method map, etc.

def applyDynamic(name: String)(args: Any*) : Any = {
println("applyDynamic: " + name)
}
}
21 changes: 18 additions & 3 deletions src/test/scala/cafebabe/test/DynamicLoading.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,35 @@ import cafebabe.AbstractByteCodes._

import org.scalatest.FunSuite
class DynamicLoading extends FunSuite {
test("DL 1") {
private def mkMinimalClassFile : ClassFile = {
val cf = new ClassFile("MyTest", None)
cf.addDefaultConstructor
val ch = cf.addMethod("I", "plusOne", "I").codeHandler
ch << ILoad(1) << Ldc(1) << IADD << IRETURN
ch.freeze
cf
}

test("DL 1") {
val cf = mkMinimalClassFile

cf.dynamicallyLoad
val cl = new CafebabeClassLoader
cl.register(cf)

val c = CustomClassLoader.loadClass("MyTest")
val c = cl.loadClass("MyTest")
val o = c.newInstance().asInstanceOf[AnyRef]
val m = c.getMethod("plusOne", Integer.TYPE)

(m.invoke(o, 41 : java.lang.Integer) === 42)
}

test("DL 2") {
// NB this test is incomplete.
val cf = mkMinimalClassFile

val cl = new CafebabeClassLoader
cl.register(cf)
val dynObj = cl.newInstance("MyTest")
// dynObj.plusOne(41) would ideally work...
}
}

0 comments on commit 70072dd

Please sign in to comment.