diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2016-03-28 10:54:51 -0400 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2016-03-28 11:53:52 -0400 |
commit | bb7018817c0f41fb867cf9e81fe6f772c79bfafd (patch) | |
tree | a691b36a50b05e35f213cfce744853c2f042eb6e /stage2 | |
parent | bd75b5af0161013b26e2feda9cfcc1e152926071 (diff) | |
download | cbt-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.scala | 2 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 4 | ||||
-rw-r--r-- | stage2/Lib.scala | 100 | ||||
-rw-r--r-- | stage2/NameTransformer.scala | 161 | ||||
-rw-r--r-- | stage2/Stage2.scala | 5 |
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 { |