aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--stage1/Stage1.scala1
-rw-r--r--stage1/Stage1Lib.scala16
-rw-r--r--stage2/AdminStage2.scala2
-rw-r--r--stage2/BasicBuild.scala4
-rw-r--r--stage2/Lib.scala100
-rw-r--r--stage2/NameTransformer.scala161
-rw-r--r--stage2/Stage2.scala5
-rw-r--r--test/test.scala10
8 files changed, 223 insertions, 76 deletions
diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala
index 7278fcf..36921be 100644
--- a/stage1/Stage1.scala
+++ b/stage1/Stage1.scala
@@ -55,7 +55,6 @@ object Stage1{
val deps = Dependencies(
JavaDependency("net.incongru.watchservice","barbary-watchservice","1.0"),
- JavaDependency("org.scala-lang","scala-reflect",constants.scalaVersion),
JavaDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r")
)
diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala
index c8af672..69b828b 100644
--- a/stage1/Stage1Lib.scala
+++ b/stage1/Stage1Lib.scala
@@ -42,22 +42,6 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{
def scalaMajorVersion(scalaMinorVersion: String) = scalaMinorVersion.split("\\.").take(2).mkString(".")
- // ========== reflection ==========
-
- /** Create instance of the given class via reflection */
- /*
- def create(cls: String)(args: Any*)(classLoader: ClassLoader): Any = {
- logger.composition( logger.showInvocation("Stage1Lib.create", (cls,args,classLoader)) )
- import scala.reflect.runtime.universe._
- val m = runtimeMirror(classLoader)
- val sym = m.classSymbol(classLoader.loadClass(cls))
- val cm = m.reflectClass( sym.asClass )
- val tpe = sym.toType
- val ctorm = cm.reflectConstructor( tpe.decl(termNames.CONSTRUCTOR).asMethod )
- ctorm(args:_*)
- }
- */
-
// ========== file system / net ==========
def array2hex(padTo: Int, array: Array[Byte]): String = {
diff --git a/stage2/AdminStage2.scala b/stage2/AdminStage2.scala
index d923b22..cd35cfe 100644
--- a/stage2/AdminStage2.scala
+++ b/stage2/AdminStage2.scala
@@ -7,7 +7,7 @@ object AdminStage2{
val lib = new Lib(init.logger)
val adminTasks = new AdminTasks(lib, args, new File(_args(0)))
new lib.ReflectObject(adminTasks){
- def usage: String = "Available methods: " ++ lib.taskNames(subclassType).mkString(" ")
+ def usage: String = "Available methods: " ++ lib.taskNames(adminTasks.getClass).mkString(" ")
}.callNullary(args.lift(0))
}
}
diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala
index acd49af..28360aa 100644
--- a/stage2/BasicBuild.scala
+++ b/stage2/BasicBuild.scala
@@ -2,7 +2,6 @@ package cbt
import cbt.paths._
import java.io._
-import java.lang.reflect.InvocationTargetException
import java.net._
import java.nio.file.{Path =>_,_}
import java.nio.file.Files.readAllBytes
@@ -10,7 +9,6 @@ import java.security.MessageDigest
import java.util.jar._
import scala.collection.immutable.Seq
-import scala.reflect.runtime.{universe => ru}
import scala.util._
class BasicBuild( context: Context ) extends Build( context )
@@ -25,7 +23,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{
def enableConcurrency = false
final def projectDirectory: File = lib.realpath(context.cwd)
assert( projectDirectory.exists, "projectDirectory does not exist: " ++ projectDirectory.string )
- final def usage: Unit = new lib.ReflectBuild(this).usage
+ final def usage: String = lib.usage(this.getClass, context)
// ========== meta data ==========
diff --git a/stage2/Lib.scala b/stage2/Lib.scala
index 99f578a..d917e8b 100644
--- a/stage2/Lib.scala
+++ b/stage2/Lib.scala
@@ -8,9 +8,9 @@ import java.nio.file.{Path =>_,_}
import java.nio.file.Files.readAllBytes
import java.security.MessageDigest
import java.util.jar._
+import java.lang.reflect.Method
import scala.collection.immutable.Seq
-import scala.reflect.runtime.{universe => ru}
import scala.util._
// pom model
@@ -125,60 +125,63 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
}
// task reflection helpers
- import ru._
- private lazy val anyRefMembers: Set[String] = ru.typeOf[AnyRef].members.toSet.map(taskName)
- def taskNames(tpe: Type): Seq[String] = tpe.members.toVector.flatMap(lib.toTask).map(taskName).sorted
- private def taskName(method: Symbol): String = method.name.decodedName.toString
- def toTask(symbol: Symbol): Option[MethodSymbol] = {
- Option(symbol)
- .filter(_.isPublic)
- .filter(_.isMethod)
- .map(_.asMethod)
- .filter(_.paramLists.flatten.size == 0)
- .filterNot(taskName(_) contains "$")
- .filterNot(t => anyRefMembers contains taskName(t))
- }
+ def tasks(cls:Class[_]): Map[String, Method] =
+ Stream
+ .iterate(cls.asInstanceOf[Class[Any]])(_.getSuperclass)
+ .takeWhile(_ != null)
+ .toVector
+ .dropRight(1) // drop Object
+ .reverse
+ .flatMap(
+ c =>
+ c
+ .getDeclaredMethods
+ .filterNot( _.getName contains "$" )
+ .filter{ m =>
+ java.lang.reflect.Modifier.isPublic(m.getModifiers)
+ }
+ .filter( _.getParameterCount == 0 )
+ .map(m => NameTransformer.decode(m.getName) -> m)
+ ).toMap
+
+ def taskNames(cls: Class[_]): Seq[String] = tasks(cls).keys.toVector.sorted
- class ReflectBuild(val build: Build) extends ReflectObject(build){
- def usage: String = {
- val baseTasks = lib.taskNames(ru.typeOf[Build])
- val thisTasks = lib.taskNames(subclassType) diff baseTasks
+ def usage(buildClass: Class[_], context: Context): String = {
+ val baseTasks = lib.taskNames(classOf[Build])
+ val thisTasks = lib.taskNames(buildClass) diff baseTasks
+ (
(
- (
- if( thisTasks.nonEmpty ){
- s"""Methods provided by Build ${build.context.cwd}
+ if( thisTasks.nonEmpty ){
+ s"""Methods provided by Build ${context}
${thisTasks.mkString(" ")}
"""
- } else ""
- ) ++ s"""Methods provided by CBT (but possibly overwritten)
+ } else ""
+ ) ++ s"""Methods provided by CBT (but possibly overwritten)
${baseTasks.mkString(" ")}"""
) ++ "\n"
- }
}
+ class ReflectBuild[T:scala.reflect.ClassTag](build: Build) extends ReflectObject(build){
+ def usage = lib.usage(build.getClass, build.context)
+ }
abstract class ReflectObject[T:scala.reflect.ClassTag](obj: T){
- lazy val mirror = ru.runtimeMirror(obj.getClass.getClassLoader)
- lazy val subclassType = mirror.classSymbol(obj.getClass).toType
def usage: String
def callNullary( taskName: Option[String] ): Unit = {
- taskName
- .map{ n => subclassType.member(ru.TermName(n).encodedName) }
- .filter(_ != ru.NoSymbol)
- .flatMap(toTask _)
- .map{ methodSymbol =>
- val result = mirror.reflect(obj).reflectMethod(methodSymbol)()
-
+ val ts = tasks(obj.getClass)
+ taskName.map( NameTransformer.encode ).flatMap(ts.get).map{ method =>
+ val result: Option[Any] = Option(method.invoke(obj)) // null in case of Unit
+ result.map{
+ value =>
// Try to render console representation. Probably not the best way to do this.
- scala.util.Try( result.getClass.getDeclaredMethod("toConsole") ) match {
- case scala.util.Success(m) =>
- println(m.invoke(result))
+ scala.util.Try( value.getClass.getDeclaredMethod("toConsole") ) match {
+ case scala.util.Success(toConsole) =>
+ println(toConsole.invoke(value))
- case scala.util.Failure(e) if e.getMessage contains "toConsole" =>
- result match {
- case () => ""
+ case scala.util.Failure(e) if Option(e.getMessage).getOrElse("") contains "toConsole" =>
+ value match {
case ExitCode(code) => System.exit(code)
case other => println( other.toString ) // no method .toConsole, using to String
}
@@ -186,16 +189,17 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{
case scala.util.Failure(e) =>
throw e
}
- }.getOrElse{
- taskName.foreach{ n =>
- System.err.println(s"Method not found: $n")
- System.err.println("")
- }
- System.err.println(usage)
- taskName.foreach{ _ =>
- ExitCode.Failure
- }
+ }.getOrElse("")
+ }.getOrElse{
+ taskName.foreach{ n =>
+ System.err.println(s"Method not found: $n")
+ System.err.println("")
}
+ System.err.println(usage)
+ taskName.foreach{ _ =>
+ ExitCode.Failure
+ }
+ }
}
}
diff --git a/stage2/NameTransformer.scala b/stage2/NameTransformer.scala
new file mode 100644
index 0000000..33489ca
--- /dev/null
+++ b/stage2/NameTransformer.scala
@@ -0,0 +1,161 @@
+// Adapted from https://github.com/scala/scala/blob/5cb3d4ec14488ce2fc5a1cc8ebdd12845859c57d/src/library/scala/reflect/NameTransformer.scala
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+package cbt
+
+/** Provides functions to encode and decode Scala symbolic names.
+ * Also provides some constants.
+ */
+object NameTransformer {
+ // XXX Short term: providing a way to alter these without having to recompile
+ // the compiler before recompiling the compiler.
+ val MODULE_SUFFIX_STRING = sys.props.getOrElse("SCALA_MODULE_SUFFIX_STRING", "$")
+ val NAME_JOIN_STRING = sys.props.getOrElse("SCALA_NAME_JOIN_STRING", "$")
+ val MODULE_INSTANCE_NAME = "MODULE$"
+ val LOCAL_SUFFIX_STRING = " "
+ val SETTER_SUFFIX_STRING = "_$eq"
+ val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$"
+
+ private val nops = 128
+ private val ncodes = 26 * 26
+
+ private class OpCodes(val op: Char, val code: String, val next: OpCodes)
+
+ private val op2code = new Array[String](nops)
+ private val code2op = new Array[OpCodes](ncodes)
+ private def enterOp(op: Char, code: String) = {
+ op2code(op.toInt) = code
+ val c = (code.charAt(1) - 'a') * 26 + code.charAt(2) - 'a'
+ code2op(c.toInt) = new OpCodes(op, code, code2op(c))
+ }
+
+ /* Note: decoding assumes opcodes are only ever lowercase. */
+ enterOp('~', "$tilde")
+ enterOp('=', "$eq")
+ enterOp('<', "$less")
+ enterOp('>', "$greater")
+ enterOp('!', "$bang")
+ enterOp('#', "$hash")
+ enterOp('%', "$percent")
+ enterOp('^', "$up")
+ enterOp('&', "$amp")
+ enterOp('|', "$bar")
+ enterOp('*', "$times")
+ enterOp('/', "$div")
+ enterOp('+', "$plus")
+ enterOp('-', "$minus")
+ enterOp(':', "$colon")
+ enterOp('\\', "$bslash")
+ enterOp('?', "$qmark")
+ enterOp('@', "$at")
+
+ /** Replace operator symbols by corresponding `\$opname`.
+ *
+ * @param name the string to encode
+ * @return the string with all recognized opchars replaced with their encoding
+ */
+ def encode(name: String): String = {
+ var buf: StringBuilder = null
+ val len = name.length()
+ var i = 0
+ while (i < len) {
+ val c = name charAt i
+ if (c < nops && (op2code(c.toInt) ne null)) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(op2code(c.toInt))
+ /* Handle glyphs that are not valid Java/JVM identifiers */
+ }
+ else if (!Character.isJavaIdentifierPart(c)) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append("$u%04X".format(c.toInt))
+ }
+ else if (buf ne null) {
+ buf.append(c)
+ }
+ i += 1
+ }
+ if (buf eq null) name else buf.toString()
+ }
+
+ /** Replace `\$opname` by corresponding operator symbol.
+ *
+ * @param name0 the string to decode
+ * @return the string with all recognized operator symbol encodings replaced with their name
+ */
+ def decode(name0: String): String = {
+ //System.out.println("decode: " + name);//DEBUG
+ val name = if (name0.endsWith("<init>")) name0.stripSuffix("<init>") + "this"
+ else name0
+ var buf: StringBuilder = null
+ val len = name.length()
+ var i = 0
+ while (i < len) {
+ var ops: OpCodes = null
+ var unicode = false
+ val c = name charAt i
+ if (c == '$' && i + 2 < len) {
+ val ch1 = name.charAt(i+1)
+ if ('a' <= ch1 && ch1 <= 'z') {
+ val ch2 = name.charAt(i+2)
+ if ('a' <= ch2 && ch2 <= 'z') {
+ ops = code2op((ch1 - 'a') * 26 + ch2 - 'a')
+ while ((ops ne null) && !name.startsWith(ops.code, i)) ops = ops.next
+ if (ops ne null) {
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(ops.op)
+ i += ops.code.length()
+ }
+ /* Handle the decoding of Unicode glyphs that are
+ * not valid Java/JVM identifiers */
+ } else if ((len - i) >= 6 && // Check that there are enough characters left
+ ch1 == 'u' &&
+ ((Character.isDigit(ch2)) ||
+ ('A' <= ch2 && ch2 <= 'F'))) {
+ /* Skip past "$u", next four should be hexadecimal */
+ val hex = name.substring(i+2, i+6)
+ try {
+ val str = Integer.parseInt(hex, 16).toChar
+ if (buf eq null) {
+ buf = new StringBuilder()
+ buf.append(name.substring(0, i))
+ }
+ buf.append(str)
+ /* 2 for "$u", 4 for hexadecimal number */
+ i += 6
+ unicode = true
+ } catch {
+ case _:NumberFormatException =>
+ /* `hex` did not decode to a hexadecimal number, so
+ * do nothing. */
+ }
+ }
+ }
+ }
+ /* If we didn't see an opcode or encoded Unicode glyph, and the
+ buffer is non-empty, write the current character and advance
+ one */
+ if ((ops eq null) && !unicode) {
+ if (buf ne null)
+ buf.append(c)
+ i += 1
+ }
+ }
+ //System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG
+ if (buf eq null) name else buf.toString()
+ }
+}
diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala
index a0a2b57..75be3c7 100644
--- a/stage2/Stage2.scala
+++ b/stage2/Stage2.scala
@@ -47,8 +47,9 @@ object Stage2{
scala.util.control.Breaks.break
case file if triggerFiles.exists(file.toString startsWith _.toString) =>
- val reflectBuild = new lib.ReflectBuild( lib.loadDynamic(context) )
- logger.loop(s"Re-running $task for " ++ reflectBuild.build.projectDirectory.toString)
+ val build = lib.loadDynamic(context)
+ val reflectBuild = new lib.ReflectBuild( build )
+ logger.loop(s"Re-running $task for " ++ build.projectDirectory.toString)
reflectBuild.callNullary(task)
}
} else {
diff --git a/test/test.scala b/test/test.scala
index ebcaaa1..e623d34 100644
--- a/test/test.scala
+++ b/test/test.scala
@@ -41,7 +41,7 @@ object Main{
}
case class Result(exit0: Boolean, out: String, err: String)
def assertSuccess(res: Result, msg: => String)(implicit logger: Logger) = {
- assert(res.exit0, msg + res.toString)
+ assert(res.exit0, msg ++ res.toString)
}
// tests
@@ -49,14 +49,14 @@ object Main{
val usageString = "Methods provided by CBT"
val res = runCbt(path, Seq())
logger.test(res.toString)
- val debugToken = "usage " + path +" "
+ val debugToken = "usage " ++ path ++ " "
assertSuccess(res,debugToken)
- assert(res.out == "", debugToken+ res.toString)
- assert(res.err contains usageString, debugToken+res.toString)
+ assert(res.out == "", debugToken ++ res.toString)
+ assert(res.err contains usageString, debugToken ++ res.toString)
}
def compile(path: String)(implicit logger: Logger) = {
val res = runCbt(path, Seq("compile"))
- val debugToken = "compile " + path +" "
+ val debugToken = "compile " ++ path ++ " "
assertSuccess(res,debugToken)
// assert(res.err == "", res.err) // FIXME: enable this
}