@@ -7,11 +7,15 @@ import java.io.{File => JFile}
7
7
import java .net .URL
8
8
import java .nio .file .{FileSystems , Files }
9
9
10
+ import dotty .tools .JDK9Reflectors
11
+ import dotty .tools .dotc .classpath .PackageNameUtils .{packageContains , separatePkgAndClassNames }
10
12
import dotty .tools .io .{AbstractFile , PlainFile , ClassPath , ClassRepresentation , EfficientClassPath }
11
13
import FileUtils ._
14
+ import PlainFile .toPlainFile
12
15
13
16
import scala .collection .JavaConverters ._
14
17
import scala .collection .immutable .ArraySeq
18
+ import scala .util .control .NonFatal
15
19
16
20
/**
17
21
* A trait allowing to look for classpath entries in directories. It provides common logic for
@@ -111,7 +115,7 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
111
115
else Array ()
112
116
}
113
117
protected def getName (f : JFile ): String = f.getName
114
- protected def toAbstractFile (f : JFile ): AbstractFile = new PlainFile ( new dotty.tools.io. File ( f.toPath))
118
+ protected def toAbstractFile (f : JFile ): AbstractFile = f.toPath.toPlainFile
115
119
protected def isPackage (f : JFile ): Boolean = f.isPackage
116
120
117
121
assert(dir != null , " Directory file in DirectoryFileLookup cannot be null" )
@@ -122,15 +126,33 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
122
126
123
127
object JrtClassPath {
124
128
import java .nio .file ._ , java .net .URI
125
- def apply (): Option [ClassPath ] =
126
- try {
127
- val fs = FileSystems .getFileSystem(URI .create(" jrt:/" ))
128
- Some (new JrtClassPath (fs))
129
- }
130
- catch {
131
- case _ : ProviderNotFoundException | _ : FileSystemNotFoundException =>
132
- None
129
+ def apply (release : Option [String ]): Option [ClassPath ] = {
130
+ import scala .util .Properties ._
131
+ if (! isJavaAtLeast(" 9" )) None
132
+ else {
133
+ // Longer term we'd like an official API for this in the JDK
134
+ // Discussion: http://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/thread.html#11738
135
+
136
+ val currentMajorVersion : Int = JDK9Reflectors .runtimeVersionMajor(JDK9Reflectors .runtimeVersion()).intValue()
137
+ release match {
138
+ case Some (v) if v.toInt < currentMajorVersion =>
139
+ try {
140
+ val ctSym = Paths .get(javaHome).resolve(" lib" ).resolve(" ct.sym" )
141
+ if (Files .notExists(ctSym)) None
142
+ else Some (new CtSymClassPath (ctSym, v.toInt))
143
+ } catch {
144
+ case NonFatal (_) => None
145
+ }
146
+ case _ =>
147
+ try {
148
+ val fs = FileSystems .getFileSystem(URI .create(" jrt:/" ))
149
+ Some (new JrtClassPath (fs))
150
+ } catch {
151
+ case _ : ProviderNotFoundException | _ : FileSystemNotFoundException => None
152
+ }
153
+ }
133
154
}
155
+ }
134
156
}
135
157
136
158
/**
@@ -157,20 +179,15 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
157
179
/** Empty string represents root package */
158
180
override private [dotty] def hasPackage (pkg : PackageName ): Boolean = packageToModuleBases.contains(pkg.dottedString)
159
181
160
- override private [dotty] def packages (inPackage : PackageName ): Seq [PackageEntry ] = {
161
- def matches (packageDottedName : String ) =
162
- if (packageDottedName.contains(" ." ))
163
- packageOf(packageDottedName) == inPackage.dottedString
164
- else inPackage.isRoot
165
- packageToModuleBases.keysIterator.filter(matches).map(PackageEntryImpl (_)).toVector
166
- }
182
+ override private [dotty] def packages (inPackage : PackageName ): Seq [PackageEntry ] =
183
+ packageToModuleBases.keysIterator.filter(pack => packageContains(inPackage.dottedString, pack)).map(PackageEntryImpl (_)).toVector
167
184
168
185
private [dotty] def classes (inPackage : PackageName ): Seq [ClassFileEntry ] =
169
186
if (inPackage.isRoot) Nil
170
187
else
171
188
packageToModuleBases.getOrElse(inPackage.dottedString, Nil ).flatMap(x =>
172
189
Files .list(x.resolve(inPackage.dirPathTrailingSlash)).iterator().asScala.filter(_.getFileName.toString.endsWith(" .class" ))).map(x =>
173
- ClassFileEntryImpl (new PlainFile ( new dotty.tools.io. File (x)) )).toVector
190
+ ClassFileEntryImpl (x.toPlainFile )).toVector
174
191
175
192
override private [dotty] def list (inPackage : PackageName ): ClassPathEntries =
176
193
if (inPackage.isRoot) ClassPathEntries (packages(inPackage), Nil )
@@ -184,14 +201,75 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
184
201
def findClassFile (className : String ): Option [AbstractFile ] =
185
202
if (! className.contains(" ." )) None
186
203
else {
187
- val inPackage = packageOf (className)
188
- packageToModuleBases.getOrElse(inPackage, Nil ).iterator.flatMap{x =>
204
+ val ( inPackage, _) = separatePkgAndClassNames (className)
205
+ packageToModuleBases.getOrElse(inPackage, Nil ).iterator.flatMap{ x =>
189
206
val file = x.resolve(FileUtils .dirPath(className) + " .class" )
190
- if (Files .exists(file)) new PlainFile ( new dotty.tools.io. File ( file)) :: Nil else Nil
207
+ if (Files .exists(file)) file.toPlainFile :: Nil else Nil
191
208
}.take(1 ).toList.headOption
192
209
}
193
- private def packageOf (dottedClassName : String ): String =
194
- dottedClassName.substring(0 , dottedClassName.lastIndexOf(" ." ))
210
+ }
211
+
212
+ /**
213
+ * Implementation `ClassPath` based on the \$JAVA_HOME/lib/ct.sym backing http://openjdk.java.net/jeps/247
214
+ */
215
+ final class CtSymClassPath (ctSym : java.nio.file.Path , release : Int ) extends ClassPath with NoSourcePaths {
216
+ import java .nio .file .Path , java .nio .file ._
217
+
218
+ private val fileSystem : FileSystem = FileSystems .newFileSystem(ctSym, null : ClassLoader )
219
+ private val root : Path = fileSystem.getRootDirectories.iterator.next
220
+ private val roots = Files .newDirectoryStream(root).iterator.asScala.toList
221
+
222
+ // http://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/011737.html
223
+ private def codeFor (major : Int ): String = if (major < 10 ) major.toString else ('A' + (major - 10 )).toChar.toString
224
+
225
+ private val releaseCode : String = codeFor(release)
226
+ private def fileNameMatchesRelease (fileName : String ) = ! fileName.contains(" -" ) && fileName.contains(releaseCode) // exclude `9-modules`
227
+ private val rootsForRelease : List [Path ] = roots.filter(root => fileNameMatchesRelease(root.getFileName.toString))
228
+
229
+ // e.g. "java.lang" -> Seq(/876/java/lang, /87/java/lang, /8/java/lang))
230
+ private val packageIndex : scala.collection.Map [String , scala.collection.Seq [Path ]] = {
231
+ val index = collection.mutable.AnyRefMap [String , collection.mutable.ListBuffer [Path ]]()
232
+ val isJava12OrHigher = scala.util.Properties .isJavaAtLeast(" 12" )
233
+ rootsForRelease.foreach(root => Files .walk(root).iterator().asScala.filter(Files .isDirectory(_)).foreach { p =>
234
+ val moduleNamePathElementCount = if (isJava12OrHigher) 1 else 0
235
+ if (p.getNameCount > root.getNameCount + moduleNamePathElementCount) {
236
+ val packageDotted = p.subpath(moduleNamePathElementCount + root.getNameCount, p.getNameCount).toString.replace('/' , '.' )
237
+ index.getOrElseUpdate(packageDotted, new collection.mutable.ListBuffer ) += p
238
+ }
239
+ })
240
+ index
241
+ }
242
+
243
+ /** Empty string represents root package */
244
+ override private [dotty] def hasPackage (pkg : PackageName ) = packageIndex.contains(pkg.dottedString)
245
+ override private [dotty] def packages (inPackage : PackageName ): Seq [PackageEntry ] = {
246
+ packageIndex.keysIterator.filter(pack => packageContains(inPackage.dottedString, pack)).map(PackageEntryImpl (_)).toVector
247
+ }
248
+ private [dotty] def classes (inPackage : PackageName ): Seq [ClassFileEntry ] = {
249
+ if (inPackage.isRoot) Nil
250
+ else {
251
+ val sigFiles = packageIndex.getOrElse(inPackage.dottedString, Nil ).iterator.flatMap(p =>
252
+ Files .list(p).iterator.asScala.filter(_.getFileName.toString.endsWith(" .sig" )))
253
+ sigFiles.map(f => ClassFileEntryImpl (f.toPlainFile)).toVector
254
+ }
255
+ }
256
+
257
+ override private [dotty] def list (inPackage : PackageName ): ClassPathEntries =
258
+ if (inPackage.isRoot) ClassPathEntries (packages(inPackage), Nil )
259
+ else ClassPathEntries (packages(inPackage), classes(inPackage))
260
+
261
+ def asURLs : Seq [URL ] = Nil
262
+ def asClassPathStrings : Seq [String ] = Nil
263
+ def findClassFile (className : String ): Option [AbstractFile ] = {
264
+ if (! className.contains(" ." )) None
265
+ else {
266
+ val (inPackage, classSimpleName) = separatePkgAndClassNames(className)
267
+ packageIndex.getOrElse(inPackage, Nil ).iterator.flatMap { p =>
268
+ val path = p.resolve(classSimpleName + " .sig" )
269
+ if (Files .exists(path)) path.toPlainFile :: Nil else Nil
270
+ }.take(1 ).toList.headOption
271
+ }
272
+ }
195
273
}
196
274
197
275
case class DirectoryClassPath (dir : JFile ) extends JFileDirectoryLookup [ClassFileEntryImpl ] with NoSourcePaths {
@@ -201,9 +279,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile
201
279
val relativePath = FileUtils .dirPath(className)
202
280
val classFile = new JFile (dir, relativePath + " .class" )
203
281
if (classFile.exists) {
204
- val wrappedClassFile = new dotty.tools.io.File (classFile.toPath)
205
- val abstractClassFile = new PlainFile (wrappedClassFile)
206
- Some (abstractClassFile)
282
+ Some (classFile.toPath.toPlainFile)
207
283
}
208
284
else None
209
285
}
@@ -228,11 +304,7 @@ case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFi
228
304
.map(ext => new JFile (dir, relativePath + " ." + ext))
229
305
.collectFirst { case file if file.exists() => file }
230
306
231
- sourceFile.map { file =>
232
- val wrappedSourceFile = new dotty.tools.io.File (file.toPath)
233
- val abstractSourceFile = new PlainFile (wrappedSourceFile)
234
- abstractSourceFile
235
- }
307
+ sourceFile.map(_.toPath.toPlainFile)
236
308
}
237
309
238
310
private [dotty] def sources (inPackage : PackageName ): Seq [SourceFileEntry ] = files(inPackage)
0 commit comments