summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/util/Javap.scala
blob: 21137aca56b308d7a69bcd3db5b3ea8640574a3b (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
/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author Paul Phillips
 */

package scala.tools
package util

import java.lang.reflect.{ GenericSignatureFormatError, Method, Constructor }
import java.lang.{ ClassLoader => JavaClassLoader }
import scala.tools.nsc.util.ScalaClassLoader
import java.io.{ InputStream, PrintWriter, ByteArrayInputStream, FileNotFoundException }
import scala.tools.nsc.io.File
import Javap._
import scala.language.reflectiveCalls

trait Javap {
  def loader: ScalaClassLoader
  def printWriter: PrintWriter
  def apply(args: Seq[String]): List[JpResult]
  def tryFile(path: String): Option[Array[Byte]]
  def tryClass(path: String): Array[Byte]
}

object NoJavap extends Javap {
  def loader: ScalaClassLoader                   = getClass.getClassLoader
  def printWriter: PrintWriter                   = new PrintWriter(System.err, true)
  def apply(args: Seq[String]): List[JpResult]   = Nil
  def tryFile(path: String): Option[Array[Byte]] = None
  def tryClass(path: String): Array[Byte]        = Array()
}

class JavapClass(
  val loader: ScalaClassLoader = ScalaClassLoader.appLoader,
  val printWriter: PrintWriter = new PrintWriter(System.out, true)
) extends Javap {

  lazy val parser = new JpOptions

  val EnvClass     = loader.tryToInitializeClass[FakeEnvironment](Env).orNull
  val PrinterClass = loader.tryToInitializeClass[FakePrinter](Printer).orNull
  private def failed = (EnvClass eq null) || (PrinterClass eq null)

  val PrinterCtr   = (
    if (failed) null
    else PrinterClass.getConstructor(classOf[InputStream], classOf[PrintWriter], EnvClass)
  )

  def findBytes(path: String): Array[Byte] =
    tryFile(path) getOrElse tryClass(path)

  def apply(args: Seq[String]): List[JpResult] = {
    if (failed) List(new JpError("Could not load javap tool. Check that JAVA_HOME is correct."))
    else args.toList filterNot (_ startsWith "-") map { path =>
      val bytes = findBytes(path)
      if (bytes.isEmpty) new JpError("Could not find class bytes for '%s'".format(path))
      else new JpSuccess(newPrinter(new ByteArrayInputStream(bytes), newEnv(args)))
    }
  }

  def newPrinter(in: InputStream, env: FakeEnvironment): FakePrinter =
    if (failed) null
    else PrinterCtr.newInstance(in, printWriter, env)

  def newEnv(opts: Seq[String]): FakeEnvironment = {
    lazy val env: FakeEnvironment = EnvClass.newInstance()

    if (failed) null
    else parser(opts) foreach { case (name, value) =>
      val field = EnvClass getDeclaredField name
      field setAccessible true
      field.set(env, value.asInstanceOf[AnyRef])
    }

    env
  }

  /** Assume the string is a path and try to find the classfile
   *  it represents.
   */
  def tryFile(path: String): Option[Array[Byte]] = {
    val file = File(
      if (path.endsWith(".class")) path
      else path.replace('.', '/') + ".class"
    )
    if (!file.exists) None
    else try Some(file.toByteArray) catch { case x: Exception => None }
  }
  /** Assume the string is a fully qualified class name and try to
   *  find the class object it represents.
   */
  def tryClass(path: String): Array[Byte] = {
    val extName = (
      if (path endsWith ".class") (path dropRight 6).replace('/', '.')
      else path
    )
    loader.classBytes(extName)
  }
}

object Javap {
  val Env     = "sun.tools.javap.JavapEnvironment"
  val Printer = "sun.tools.javap.JavapPrinter"

  def isAvailable(cl: ScalaClassLoader = ScalaClassLoader.appLoader) =
    cl.tryToInitializeClass[AnyRef](Env).isDefined

  // "documentation"
  type FakeEnvironment = AnyRef
  type FakePrinter = AnyRef

  def apply(path: String): Unit      = apply(Seq(path))
  def apply(args: Seq[String]): Unit = new JavapClass() apply args foreach (_.show())

  sealed trait JpResult {
    type ResultType
    def isError: Boolean
    def value: ResultType
    def show(): Unit
    // todo
    // def header(): String
    // def fields(): List[String]
    // def methods(): List[String]
    // def signatures(): List[String]
  }
  class JpError(msg: String) extends JpResult {
    type ResultType = String
    def isError = true
    def value = msg
    def show() = println(msg)
  }
  class JpSuccess(val value: AnyRef) extends JpResult {
    type ResultType = AnyRef
    def isError = false
    def show() = value.asInstanceOf[{ def print(): Unit }].print()
  }

  class JpOptions {
    private object Access {
      final val PRIVATE = 0
      final val PROTECTED = 1
      final val PACKAGE = 2
      final val PUBLIC = 3
    }
    private val envActionMap: Map[String, (String, Any)] = {
      val map = Map(
        "-l"         -> (("showLineAndLocal", true)),
        "-c"         -> (("showDisassembled", true)),
        "-s"         -> (("showInternalSigs", true)),
        "-verbose"   -> (("showVerbose", true)),
        "-private"   -> (("showAccess", Access.PRIVATE)),
        "-package"   -> (("showAccess", Access.PACKAGE)),
        "-protected" -> (("showAccess", Access.PROTECTED)),
        "-public"    -> (("showAccess", Access.PUBLIC)),
        "-all"       -> (("showallAttr", true))
      )
      map ++ List(
        "-v" -> map("-verbose"),
        "-p" -> map("-private")
      )
    }
    def apply(opts: Seq[String]): Seq[(String, Any)] = {
      opts flatMap { opt =>
        envActionMap get opt match {
          case Some(pair) => List(pair)
          case _          =>
            val charOpts = opt.tail.toSeq map ("-" + _)
            if (charOpts forall (envActionMap contains _))
              charOpts map envActionMap
            else Nil
        }
      }
    }
  }
}