diff --git a/build.sbt b/build.sbt index a58bee5..31f693b 100644 --- a/build.sbt +++ b/build.sbt @@ -8,5 +8,7 @@ scalacOptions += "-deprecation" scalacOptions += "-unchecked" +scalacOptions += "-Xexperimental" + libraryDependencies += "org.scalatest" %% "scalatest" % "1.8" diff --git a/src/main/scala/cafebabe/CafebabeClassLoader.scala b/src/main/scala/cafebabe/CafebabeClassLoader.scala new file mode 100644 index 0000000..708b537 --- /dev/null +++ b/src/main/scala/cafebabe/CafebabeClassLoader.scala @@ -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] + } +} diff --git a/src/main/scala/cafebabe/ClassFile.scala b/src/main/scala/cafebabe/ClassFile.scala index ee4a0ae..65e8af4 100644 --- a/src/main/scala/cafebabe/ClassFile.scala +++ b/src/main/scala/cafebabe/ClassFile.scala @@ -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 = { diff --git a/src/main/scala/cafebabe/CustomClassLoader.scala b/src/main/scala/cafebabe/CustomClassLoader.scala deleted file mode 100644 index 7369629..0000000 --- a/src/main/scala/cafebabe/CustomClassLoader.scala +++ /dev/null @@ -1,18 +0,0 @@ -package cafebabe - -import scala.collection.mutable.{Map=>MutableMap} - -object CustomClassLoader extends ClassLoader { - private val classes : MutableMap[String,Array[Byte]] = MutableMap.empty - - def registerClass(name : String, bytes : Array[Byte]) : Unit = { - classes(name) = bytes - } - - override def findClass(name : String) : Class[_] = { - classes.get(name) match { - case Some(ba) => defineClass(name, ba, 0, ba.length) - case None => super.findClass(name) - } - } -} diff --git a/src/main/scala/cafebabe/DynObj.scala b/src/main/scala/cafebabe/DynObj.scala new file mode 100644 index 0000000..55b4851 --- /dev/null +++ b/src/main/scala/cafebabe/DynObj.scala @@ -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) + } +} diff --git a/src/test/scala/cafebabe/test/DynamicLoading.scala b/src/test/scala/cafebabe/test/DynamicLoading.scala index f82b584..091a83c 100644 --- a/src/test/scala/cafebabe/test/DynamicLoading.scala +++ b/src/test/scala/cafebabe/test/DynamicLoading.scala @@ -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... + } }