package cbt.reflect
import java.io.File
import java.lang.reflect.{ Constructor, Method, InvocationTargetException, Modifier }
import scala.reflect.ClassTag
import cbt.ExitCode
import cbt.file._
import cbt.common_1._
object `package` extends Module {
implicit class CbtClassOps( val c: Class[_] ) extends AnyVal with ops.CbtClassOps
implicit class CbtConstructorOps( val c: Constructor[_] ) extends AnyVal with ops.CbtConstructorOps
implicit class CbtMethodOps( val m: Method ) extends AnyVal with ops.CbtMethodOps
}
package ops {
trait CbtClassOps extends Any {
def c: Class[_]
def name = c.getName
def method( name: String, parameterTypes: Class[_]* ) = c.getMethod( name, parameterTypes: _* )
def methods = c.getMethods
def modifiers = c.getModifiers
def constructors = c.getConstructors
def declaredMethods = c.getDeclaredMethods
def isInterface = Modifier.isInterface( c.getModifiers )
def isAbstract = Modifier.isAbstract( c.getModifiers )
def isPrivate = Modifier.isPrivate( c.getModifiers )
def isProtected = Modifier.isProtected( c.getModifiers )
def isPublic = Modifier.isPublic( c.getModifiers )
def isFinal = Modifier.isFinal( c.getModifiers )
}
trait CbtConstructorOps extends Any {
def c: Constructor[_]
def parameterTypes = c.getParameterTypes
}
trait CbtMethodOps extends Any {
def m: Method
def name = m.getName
def declaringClass = m.getDeclaringClass
def modifiers = m.getModifiers
def parameters = m.getParameters
def parameterTypes = m.getParameterTypes
def returnType = m.getReturnType
def isAbstract = Modifier.isAbstract( m.getModifiers )
def isFinal = Modifier.isFinal( m.getModifiers )
def isNative = Modifier.isNative( m.getModifiers )
def isPrivate = Modifier.isPrivate( m.getModifiers )
def isProtected = Modifier.isProtected( m.getModifiers )
def isPublic = Modifier.isPublic( m.getModifiers )
def isStatic = Modifier.isStatic( m.getModifiers )
def isStrict = Modifier.isStrict( m.getModifiers )
def isSynchronized = Modifier.isSynchronized( m.getModifiers )
def isTransient = Modifier.isTransient( m.getModifiers )
def isVolatile = Modifier.isVolatile( m.getModifiers )
def show = (
m.name ~ "( "
~ m.parameters.map( _.getType.name ).mkString( ", " )
~ " )"
)
}
}
trait Module {
def getMain( cls: Class[_] ): StaticMethod[Seq[String], ExitCode] = {
val f = findStaticExitMethodOrFail[Array[String]]( cls, "main" )
f.copy(
function = ( args: Seq[String] ) => f.function( args.to )
)
}
def findMain( cls: Class[_] ): Option[StaticMethod[Seq[String], ExitCode]] = {
findStaticExitMethod[Array[String]]( cls, "main" )
.map( f =>
f.copy(
function = ( args: Seq[String] ) => f.function( args.to )
) )
}
/** ignoreMissingClasses allows ignoring other classes root directories which are subdirectories of this one */
def topLevelClasses(
classesRootDirectory: File,
classLoader: ClassLoader,
ignoreMissingClasses: Boolean
): Seq[Class[_]] =
topLevelClassNames( classesRootDirectory )
.map { name =>
try {
classLoader.loadClass( name )
} catch {
case e: ClassNotFoundException if ignoreMissingClasses => null
case e: NoClassDefFoundError if ignoreMissingClasses => null
}
}
.filterNot( ignoreMissingClasses && _ == null )
/** Given a directory corresponding to the root package, return
* the names of all top-level classes based on the class files found
*/
def topLevelClassNames( classesRootDirectory: File ): Seq[String] =
classesRootDirectory.listRecursive
.filter( _.isFile )
.map( _.getPath )
.collect {
// no $ to avoid inner classes
case path if !path.contains( "$" ) && path.endsWith( ".class" ) =>
path
.stripSuffix( ".class" )
.stripPrefix( classesRootDirectory.getPath )
.stripPrefix( File.separator ) // 1 for the slash
.replace( File.separator, "." )
}
def findStaticExitMethodOrFail[Arg: ClassTag](
cls: Class[_], name: String
): StaticMethod[Arg, ExitCode] = {
val f = findStaticMethodOrFail[Arg, Unit]( cls, name )
f.copy(
function = arg => trapExitCode { f.function( arg ); ExitCode.Success }
)
}
def findStaticMethodOrFail[Arg, Result](
cls: Class[_], name: String
)(
implicit
Result: ClassTag[Result], Arg: ClassTag[Arg]
): StaticMethod[Arg, Result] = {
val m = cls.method( name, Arg.runtimeClass )
assert( Result.runtimeClass.isAssignableFrom( m.returnType ) )
typeStaticMethod( m )
}
def findStaticExitMethod[Arg: ClassTag](
cls: Class[_], name: String
): Option[StaticMethod[Arg, ExitCode]] =
findStaticMethod[Arg, Unit]( cls, name ).map( f =>
f.copy(
function = arg => trapExitCode { f.function( arg ); ExitCode.Success }
) )
def findStaticMethod[Arg, Result](
cls: Class[_], name: String
)(
implicit
Result: ClassTag[Result], Arg: ClassTag[Arg]
): Option[StaticMethod[Arg, Result]] = {
Some( cls )
.filterNot( _.isAbstract )
.filterNot( _.isInterface )
.flatMap( _
.getMethods
.find( m =>
!m.isAbstract
&& m.isPublic
&& m.name == name
&& m.parameterTypes.toList == List( Arg.runtimeClass )
&& Result.runtimeClass.isAssignableFrom( m.returnType ) ) )
.map( typeStaticMethod )
}
def typeStaticMethod[Arg, Result]( method: Method ): StaticMethod[Arg, Result] = {
val m = method
val instance =
if ( m.isStatic ) null
else m.declaringClass.newInstance // Dottydoc needs this. It's main method is not static.
StaticMethod(
arg => m.invoke( instance, arg.asInstanceOf[AnyRef] ).asInstanceOf[Result],
m
)
}
def trapExitCodeOrValue[T]( result: => T, i: Int = 5 ): Either[ExitCode, T] = {
TrapSystemExit.run(
new TrapSystemExit[Either[ExitCode, T]] {
def run = Right( result )
def wrap( exitCode: Int ) = Left( new ExitCode( exitCode ) )
}
)
}
def trapExitCode( code: => ExitCode ): ExitCode =
trapExitCodeOrValue( code ).merge
}