aboutsummaryrefslogtreecommitdiff
path: root/libraries/reflect/reflect.scala
blob: c2b05ed3a84d80dd2175726d9f3e701698481d37 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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
}