summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala
blob: fd090db89e14b5cdb545893777e98ce2c0d95189 (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
/* NSC -- new Scala compiler
 * Copyright 2005-2010 LAMP/EPFL
 * @author Paul Phillips
 */

package scala.tools.nsc
package interpreter

import java.lang.reflect
import reflect.{ Modifier, AccessibleObject }
import Modifier.{ isPrivate, isProtected, isStatic }
import scala.reflect.NameTransformer
import scala.collection.mutable.HashMap
import ReflectionCompletion._

trait ReflectionCompletion extends CompletionAware {
  def clazz: Class[_]
  protected def visibleMembers: List[AccessibleObject]
  protected def memberCompletions = visibleMembers filter isPublic map reflectName

  def reflectName(m: AccessibleObject) = m match {
    case x: reflect.Method  => x.getName
    case x: reflect.Field   => x.getName
    case x                  => error(x.toString)
  }
  def isPublic(m: AccessibleObject) = m match {
    case x: reflect.Method  => Modifier isPublic x.getModifiers
    case x: reflect.Field   => Modifier isPublic x.getModifiers
    case x                  => error(x.toString)
  }

  lazy val (staticMethods, instanceMethods) = clazz.getMethods.toList partition (x => isStatic(x.getModifiers))
  lazy val (staticFields, instanceFields) = clazz.getFields.toList partition (x => isStatic(x.getModifiers))

  /** Oops, mirror classes don't descend from scalaobject.
   */
  def isScalaClazz(cl: Class[_]) = {
    (allInterfacesFor(cl) exists (_.getName == "scala.ScalaObject")) ||
    (classForName(cl.getName + "$").isDefined)
  }
  def allInterfacesFor(cl: Class[_]): List[Class[_]] = allInterfacesFor(cl, Nil)

  private def allInterfacesFor(cl: Class[_], acc: List[Class[_]]): List[Class[_]] = {
    if (cl == null) acc.distinct
    else allInterfacesFor(cl.getSuperclass, acc ::: cl.getInterfaces.toList)
  }
}

/** A completion aware object representing a single instance of some class.
 *  It completes to instance fields and methods, and delegates to another
 *  InstanceCompletion object if it can determine the result type of the element.
 */
class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion {
  protected def visibleMembers = instanceMethods ::: instanceFields
  def extras = List("isInstanceOf", "asInstanceOf", "toString")
  lazy val completions = memberCompletions ::: extras
  def completions(verbosity: Int) = completions

  val (zeroArg, otherArg) = instanceMethods partition (_.getParameterTypes.size == 0)
  override def follow(id: String) = {
    val nextClazz = zeroArg find (m => m.getName == id) map (_.getReturnType)
    if (nextClazz.isDefined) nextClazz map (x => new InstanceCompletion(x))
    else instanceFields find (_.getName == id) map (x => new InstanceCompletion(x.getType))
  }
}

/** The complementary class to InstanceCompletion.  It has logic to deal with
 *  java static members and scala companion object members.
 */
class StaticCompletion(val clazz: Class[_]) extends ReflectionCompletion {
  protected def visibleMembers = whichMethods ::: whichFields
  lazy val completions = memberCompletions
  def completions(verbosity: Int) = completions

  def className = clazz.getName
  def isJava = !isScalaClazz(clazz)

  private def whichMethods = if (isJava) staticMethods else instanceMethods
  private def whichFields = if (isJava) staticFields else instanceFields
  val (zeroArg, otherArg) = whichMethods partition (_.getParameterTypes.size == 0)

  override def follow(id: String) = {
    val nextClazz = zeroArg find (m => m.getName == id) map (_.getReturnType)
    if (nextClazz.isDefined) nextClazz map (x => new InstanceCompletion(x))
    else staticFields find (_.getName == id) map (x => new InstanceCompletion(x.getType))
  }

  override def toString = "StaticCompletion(%s) => %s".format(clazz.getName, completions)
}

object ReflectionCompletion {
  import java.io.File
  import java.util.jar.{ JarEntry, JarFile }
  import scala.tools.nsc.io.Streamable

  // XXX at the moment this is imperfect because scala's protected semantics
  // differ from java's, so protected methods appear public via reflection;
  // yet scala enforces the protection.  The result is that protected members
  // appear in completion yet cannot actually be called.  Fixing this
  // properly requires a scala.reflect.* API.  Fixing it uglily is possible
  // too (cast to structural type!) but I deem poor use of energy.
  private def skipModifiers(m: reflect.Method) = {
    import java.lang.reflect.Modifier._
    val flags = STATIC | PRIVATE | PROTECTED
    (m.getModifiers & flags) == 0
  }
  private def getAnyClass(x: Any): Class[_] = x.asInstanceOf[AnyRef].getClass

  def methodsOf(target: Any): List[String] =
    getAnyClass(target).getMethods filter skipModifiers map (_.getName) toList
}