aboutsummaryrefslogtreecommitdiff
path: root/stage2
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2016-03-28 10:54:51 -0400
committerChristopher Vogt <oss.nsp@cvogt.org>2016-03-28 11:53:52 -0400
commitbb7018817c0f41fb867cf9e81fe6f772c79bfafd (patch)
treea691b36a50b05e35f213cfce744853c2f042eb6e /stage2
parentbd75b5af0161013b26e2feda9cfcc1e152926071 (diff)
downloadcbt-bb7018817c0f41fb867cf9e81fe6f772c79bfafd.tar.gz
cbt-bb7018817c0f41fb867cf9e81fe6f772c79bfafd.tar.bz2
cbt-bb7018817c0f41fb867cf9e81fe6f772c79bfafd.zip
replace Scala reflection with Java reflection seems to fix the weird exceptions that happened in the previous commit. Also gets rid of scala.reflect dependency in stage2.
Diffstat (limited to 'stage2')
-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
5 files changed, 218 insertions, 54 deletions
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 {