From dde9ac16434e7ddb7f24504c864c78a87e043553 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sun, 2 Oct 2016 11:52:13 -0400 Subject: clean up main class discovery mechanism and offer interactive choice if multiple main classes are found --- stage1/Stage1Lib.scala | 90 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 36 deletions(-) (limited to 'stage1/Stage1Lib.scala') diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index 3a1fe45..5e65018 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -8,7 +8,7 @@ import java.nio.file._ import java.nio.file.attribute.FileTime import javax.tools._ import java.security._ -import java.util.{Set=>_,Map=>_,_} +import java.util.{Set=>_,Map=>_,List=>_,_} import java.util.concurrent.ConcurrentHashMap import javax.xml.bind.annotation.adapters.HexBinaryAdapter @@ -95,12 +95,6 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ // ========== compilation / execution ========== - def runMainIfFound(cls: String, args: Seq[String], classLoader: ClassLoader ): ExitCode = { - if( classLoader.canLoad(cls) ){ - runMain(cls, args, classLoader ) - } else ExitCode.Success - } - def runMain( cls: String, args: Seq[String], classLoader: ClassLoader, fakeInstance: Boolean = false ): ExitCode = { import java.lang.reflect.Modifier logger.lib(s"Running $cls.main($args) with classLoader: " ++ classLoader.toString) @@ -118,41 +112,65 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ } } - def mainClasses(compileTarget : File, classLoader : ClassLoader): Seq[String] = { - val targetLength = compileTarget.toString.length + 1 // compile target and slash - - def className(file : File) : String = { - file.toString.dropRight(".class".length).drop(targetLength).replace(File.separator, ".") - } - - /* for packaged applications */ - def retrieveFiles(file: File): Seq[File] = { - file.listFiles.toSeq.map(currFile => { - if (currFile.isDirectory) retrieveFiles(currFile) - else Seq(currFile) + /** shows an interactive dialogue in the shell asking the user to pick one of many choices */ + def pickOne[T]( msg: String, choices: Seq[T] )( show: T => String ): Option[T] = { + if(choices.size == 0) None else if(choices.size == 1) Some(choices.head) else { + Option(System.console).map{ + console => + val indexedChoices: Map[Int, T] = choices.zipWithIndex.toMap.mapValues(_+1).map(_.swap) + System.err.println( + indexedChoices.map{ case (index,choice) => s"[${index}] "++show(choice)}.mkString("\n") + ) + val range = s"1 - ${indexedChoices.size}" + System.err.println() + System.err.println( msg ++ " [" ++ range ++ "] " ) + val answer = console.readLine() + val choice = try{ + Some(Integer.parseInt(answer)) + }catch{ + case e:java.lang.NumberFormatException => None } - ).flatten - } - def classesWithMain(target : File, classLoader : ClassLoader): Seq[File] = { - def hasMainClass(file : File) : Boolean = { - val name = className(file) - val inspectClass = classLoader.loadClass(name) - inspectClass.getDeclaredMethods().map(_.getName).contains("main") + choice.flatMap(indexedChoices.get).orElse{ + System.err.println("Not in range "++range) + None + } + }.getOrElse{ + System.err.println("Using '"++show(choices.head)++"' because System.console() == null. Use `cbt direct ` or see https://github.com/cvogt/cbt/issues/236") + None } - - // FIXME: Check if argument list has exactly one element of type Array[String] - def isInnerClass(file : File) : Boolean = file.toString.contains("$") - val files = retrieveFiles(target) - - val classFiles = files.filter(file => file.toString.endsWith(".class") && !isInnerClass(file)).toSeq - - classFiles.filter(hasMainClass(_)) } + } - val hasMain = classesWithMain(compileTarget, classLoader) + /** interactively pick one main class */ + def runClass( mainClasses: Seq[Class[_]] ): Option[Class[_]] = { + pickOne( "Which one do you want to run?", mainClasses )( _.toString ) + } - hasMain.map(className(_)) + def mainClasses( targetDirectory: File, classLoader : ClassLoader ): Seq[Class[_]] = { + val arrayClass = classOf[Array[String]] + val unitClass = classOf[Unit] + + listFilesRecursive(targetDirectory) + .filter(_.isFile) + .map(_.getPath) + .collect{ + // no $ to avoid inner classes + case path if !path.contains("$") && path.endsWith(".class") => + classLoader.loadClass( + path + .stripSuffix(".class") + .stripPrefix(targetDirectory.getPath) + .stripPrefix(File.separator) // 1 for the slash + .replace(File.separator, ".") + ) + }.filter( + _.getDeclaredMethods().exists( m => + m.getName == "main" + && m.getParameterTypes.toList == List(arrayClass) + && m.getReturnType == unitClass + ) + ) } implicit class ClassLoaderExtensions(classLoader: ClassLoader){ -- cgit v1.2.3