@@ -7,11 +7,14 @@ import java.io.{File => JFile}
77import java .net .URL
88import java .nio .file .{FileSystems , Files }
99
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 }
1112import FileUtils ._
13+ import PlainFile .toPlainFile
1214
1315import scala .collection .JavaConverters ._
1416import scala .collection .immutable .ArraySeq
17+ import scala .util .control .NonFatal
1518
1619/**
1720 * 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
111114 else Array ()
112115 }
113116 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
115118 protected def isPackage (f : JFile ): Boolean = f.isPackage
116119
117120 assert(dir != null , " Directory file in DirectoryFileLookup cannot be null" )
@@ -122,15 +125,33 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
122125
123126object JrtClassPath {
124127 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+ }
133153 }
154+ }
134155}
135156
136157/**
@@ -157,20 +178,15 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
157178 /** Empty string represents root package */
158179 override private [dotty] def hasPackage (pkg : PackageName ): Boolean = packageToModuleBases.contains(pkg.dottedString)
159180
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
167183
168184 private [dotty] def classes (inPackage : PackageName ): Seq [ClassFileEntry ] =
169185 if (inPackage.isRoot) Nil
170186 else
171187 packageToModuleBases.getOrElse(inPackage.dottedString, Nil ).flatMap(x =>
172188 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
174190
175191 override private [dotty] def list (inPackage : PackageName ): ClassPathEntries =
176192 if (inPackage.isRoot) ClassPathEntries (packages(inPackage), Nil )
@@ -184,14 +200,75 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
184200 def findClassFile (className : String ): Option [AbstractFile ] =
185201 if (! className.contains(" ." )) None
186202 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 =>
189205 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
191207 }.take(1 ).toList.headOption
192208 }
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+ }
195272}
196273
197274case class DirectoryClassPath (dir : JFile ) extends JFileDirectoryLookup [ClassFileEntryImpl ] with NoSourcePaths {
@@ -201,9 +278,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile
201278 val relativePath = FileUtils .dirPath(className)
202279 val classFile = new JFile (dir, relativePath + " .class" )
203280 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)
207282 }
208283 else None
209284 }
@@ -228,11 +303,7 @@ case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFi
228303 .map(ext => new JFile (dir, relativePath + " ." + ext))
229304 .collectFirst { case file if file.exists() => file }
230305
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)
236307 }
237308
238309 private [dotty] def sources (inPackage : PackageName ): Seq [SourceFileEntry ] = files(inPackage)
0 commit comments