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