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

package scala.tools.nsc
package interpreter

import java.net.URL
import java.lang.reflect
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.DelayedLazyVal
import scala.util.NameTransformer.{ decode, encode }
import PackageCompletion._

/** Completion among all known packages.  It examines the jars in a
 *  separate thread so as not to slow down startup.  If it arrives at
 *  an object, it delegates to StaticCompletion for that object.
 */
class PackageCompletion(classpath: List[URL]) extends CompletionAware {
  // it takes a little while to look through the jars so we use a future and a concurrent map
  class CompletionAgent {
    val dottedPaths: ConcurrentHashMap[String, List[CompletionInfo]] = new ConcurrentHashMap[String, List[CompletionInfo]]
    val topLevelPackages = new DelayedLazyVal(
      () => enumToList(dottedPaths.keys) filterNot (_ contains '.'),
      getDottedPaths(dottedPaths, classpath)
    )
  }
  val agent = new CompletionAgent
  import agent._

  def completions() = topLevelPackages()
  override def follow(id: String) =
    if (dottedPaths containsKey id) Some(new SubCompletor(id))
    else None

  class SubCompletor(root: String) extends CompletionAware {
    private def infos = dottedPaths get root
    def completions() = infos map (_.visibleName)

    override def follow(segment: String): Option[CompletionAware] = {
      PackageCompletion.this.follow(root + "." + segment) orElse {
        for (CompletionInfo(`segment`, className, _) <- infos) {
          return Some(new StaticCompletion(className))
        }
        None
      }
    }
  }
}

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

  def enumToList[T](e: java.util.Enumeration[T]): List[T] = enumToListInternal(e, Nil)
  private def enumToListInternal[T](e: java.util.Enumeration[T], xs: List[T]): List[T] =
    if (e == null || !e.hasMoreElements) xs else enumToListInternal(e, e.nextElement :: xs)

  def getClassFiles(path: String): List[String] = {
    def exists(path: String) = { new File(path) exists }
    if (!exists(path)) return Nil

    (enumToList(new JarFile(path).entries) map (_.getName)) .
      partialMap { case x: String if x endsWith ".class" => x dropRight 6 } .
      filterNot { ReflectionCompletion.shouldHide }
  }

  case class CompletionInfo(visibleName: String, className: String, jar: String) {
    lazy val jarfile = new JarFile(jar)
    lazy val entry = jarfile getEntry className

    override def hashCode = visibleName.hashCode
    override def equals(other: Any) = other match {
      case x: CompletionInfo  => visibleName == x.visibleName
      case _                  => false
    }

    def getBytes(): Array[Byte] = {
      if (entry == null) Array() else {
        val x = new Streamable.Bytes { def inputStream() = jarfile getInputStream entry }
        x.toByteArray()
      }
    }
  }

  // all the dotted path to classfiles we can find by poking through the jars
  def getDottedPaths(map: ConcurrentHashMap[String, List[CompletionInfo]], classpath: List[URL]): Unit = {
    val cp = classpath map (_.getPath)
    val jars = cp.removeDuplicates filter (_ endsWith ".jar")

    // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C)
    // and scala.Range$BigInt needs to go scala -> Range -> BigInt
    def subpaths(s: String): List[(String, String)] = {
      val segs = decode(s).split("""[/.]""")
      val components = segs dropRight 1

      (1 to components.length).toList flatMap { i =>
        val k = components take i mkString "."
        if (segs(i) contains "$") {
          val dollarsegs = segs(i).split("$").toList
          for (j <- 1 to (dollarsegs.length - 1) toList) yield {
            val newk = k + "." + (dollarsegs take j mkString ".")
            (k -> dollarsegs(j))
          }
        }
        else List(k -> segs(i))
      }
    }

    def oneJar(jar: String): Unit = {
      val classfiles = getClassFiles(jar)

      for (cl <- classfiles.removeDuplicates ; (k, _v) <- subpaths(cl)) {
        val v = CompletionInfo(_v, cl, jar)

        if (map containsKey k) {
          val vs = map.get(k)
          if (vs contains v) ()
          else map.put(k, v :: vs)
        }
        else map.put(k, List(v))
      }
    }

    jars foreach oneJar
  }
}