11package ch .epfl .scala .debugadapter .internal
22
33import ch .epfl .scala .debugadapter ._
4+ import ch .epfl .scala .debugadapter .internal .ScalaExtension .*
5+ import ch .epfl .scala .debugadapter .internal .scalasig .Decompiler
6+ import ch .epfl .scala .debugadapter .internal .scalasig .ScalaSig
47import org .objectweb .asm ._
58
69import java .net .URI
710import java .nio .file ._
8- import scala .jdk .CollectionConverters .*
911import scala .collection .mutable
10- import ClassEntryLookUp .readSourceContent
11-
12- import scala .util .matching .Regex
13- import ch .epfl .scala .debugadapter .internal .scalasig .ScalaSig
14- import ch .epfl .scala .debugadapter .internal .scalasig .Decompiler
15- import ch .epfl .scala .debugadapter .internal .ScalaExtension .*
12+ import scala .jdk .CollectionConverters .*
13+ import scala .util .Properties
1614import scala .util .Try
15+ import scala .util .matching .Regex
1716
18- private case class SourceLine ( uri : URI , lineNumber : Int )
17+ import ClassEntryLookUp . readSourceContent
1918
2019private [internal] case class ClassFile (
2120 fullyQualifiedName : String ,
@@ -35,35 +34,36 @@ private[internal] case class ClassFile(
3534private class ClassEntryLookUp (
3635 val entry : ClassEntry ,
3736 fqcnToClassFile : Map [String , ClassFile ],
38- sourceUriToSourceFile : Map [URI , SourceFile ],
39- sourceUriToClassFiles : Map [URI , Seq [ClassFile ]],
37+ sourceUriToSourceFile : Map [SourceFileKey , SourceFile ],
38+ sourceUriToClassFiles : Map [SourceFileKey , Seq [ClassFile ]],
4039 classNameToSourceFile : Map [String , SourceFile ],
4140 missingSourceFileClassFiles : Seq [ClassFile ],
4241 val orphanClassFiles : Seq [ClassFile ],
4342 logger : Logger
4443) {
45- private val cachedSourceLines = mutable.Map [SourceLine , Seq [ClassFile ]]()
44+ private val cachedSourceLines = mutable.Map [SourceLineKey , Seq [ClassFile ]]()
4645
47- def sources : Iterable [URI ] = sourceUriToSourceFile.keys
46+ def sources : Iterable [SourceFileKey ] = sourceUriToSourceFile.keys
4847 def fullyQualifiedNames : Iterable [String ] = {
4948 classNameToSourceFile.keys ++
5049 orphanClassFiles.map(_.fullyQualifiedName) ++
5150 missingSourceFileClassFiles.map(_.fullyQualifiedName)
5251 }
5352
5453 def getFullyQualifiedClassName (
55- sourceUri : URI ,
54+ sourceKey : SourceFileKey ,
5655 lineNumber : Int
5756 ): Option [String ] = {
58- val line = SourceLine (sourceUri , lineNumber)
57+ val line = SourceLineKey (sourceKey , lineNumber)
5958
6059 if (! cachedSourceLines.contains(line)) {
6160 // read and cache line numbers from class files
62- sourceUriToClassFiles(sourceUri)
61+ sourceUriToClassFiles
62+ .getOrElse(sourceKey, Nil )
6363 .groupBy(_.classSystem)
6464 .foreach { case (classSystem, classFiles) =>
6565 classSystem
66- .within((_, root) => loadLineNumbers(root, classFiles, sourceUri ))
66+ .within((_, root) => loadLineNumbers(root, classFiles, sourceKey ))
6767 .warnFailure(logger, s " Cannot load line numbers in ${classSystem.name}" )
6868 }
6969 }
@@ -80,7 +80,7 @@ private class ClassEntryLookUp(
8080 private def loadLineNumbers (
8181 root : Path ,
8282 classFiles : Seq [ClassFile ],
83- sourceUri : URI
83+ sourceKey : SourceFileKey
8484 ): Unit = {
8585 for (classFile <- classFiles) {
8686 val path = root.resolve(classFile.relativePath)
@@ -108,7 +108,7 @@ private class ClassEntryLookUp(
108108 reader.accept(visitor, 0 )
109109
110110 for (n <- lineNumbers) {
111- val line = SourceLine (sourceUri , n)
111+ val line = SourceLineKey (sourceKey , n)
112112 cachedSourceLines.update(
113113 line,
114114 cachedSourceLines.getOrElse(line, Seq .empty) :+ classFile
@@ -121,16 +121,16 @@ private class ClassEntryLookUp(
121121 }
122122
123123 def getSourceContent (sourceUri : URI ): Option [String ] =
124- sourceUriToSourceFile.get(sourceUri).flatMap(readSourceContent(_, logger))
124+ sourceUriToSourceFile.get(SourceFileKey ( sourceUri) ).flatMap(readSourceContent(_, logger))
125125
126- def getSourceFile (fqcn : String ): Option [URI ] =
126+ def getSourceFileURI (fqcn : String ): Option [URI ] =
127127 classNameToSourceFile.get(fqcn).map(_.uri)
128128
129129 def getSourceContentFromClassName (fqcn : String ): Option [String ] =
130- getSourceFile (fqcn).flatMap(getSourceContent)
130+ getSourceFileURI (fqcn).flatMap(getSourceContent)
131131
132132 def getClassFiles (sourceUri : URI ): Seq [ClassFile ] =
133- sourceUriToClassFiles.get(sourceUri).getOrElse(Seq .empty)
133+ sourceUriToClassFiles.get(SourceFileKey ( sourceUri) ).getOrElse(Seq .empty)
134134
135135 def getClassFile (fqcn : String ): Option [ClassFile ] =
136136 fqcnToClassFile.get(fqcn)
@@ -145,7 +145,7 @@ private class ClassEntryLookUp(
145145 def fromSource = {
146146 val scalaSigs =
147147 for {
148- sourceFile <- getSourceFile (fqcn).toSeq
148+ sourceFile <- getSourceFileURI (fqcn).toSeq
149149 if sourceFile.toString.endsWith(" .scala" )
150150 classFile <- getClassFiles(sourceFile)
151151 if fqcn.startsWith(classFile.fullyQualifiedName + " $" )
@@ -182,21 +182,21 @@ private object ClassEntryLookUp {
182182 classFiles.map(c => (c.fullyQualifiedName, c)).toMap
183183
184184 val sourceFileToRoot = sourceLookUps.flatMap(l => l.sourceFiles.map(f => (f -> l.root))).toMap
185- val sourceUriToSourceFile = sourceLookUps.flatMap(_.sourceFiles).map(f => (f.uri, f)).toMap
185+ val sourceUriToSourceFile = sourceLookUps.flatMap(_.sourceFiles).map(f => (SourceFileKey ( f.uri) , f)).toMap
186186 val sourceNameToSourceFile = sourceLookUps.flatMap(_.sourceFiles).groupBy(f => f.fileName)
187187
188188 val classNameToSourceFile = mutable.Map [String , SourceFile ]()
189- val sourceUriToClassFiles = mutable.Map [URI , Seq [ClassFile ]]()
189+ val sourceUriToClassFiles = mutable.Map [SourceFileKey , Seq [ClassFile ]]()
190190 val orphanClassFiles = mutable.Buffer [ClassFile ]()
191191 val missingSourceFileClassFiles = mutable.Buffer [ClassFile ]()
192192
193193 for (classFile <- classFiles) {
194194 def recordSourceFile (sourceFile : SourceFile ): Unit = {
195195 classNameToSourceFile.put(classFile.fullyQualifiedName, sourceFile)
196196 sourceUriToClassFiles.update(
197- sourceFile.uri,
197+ SourceFileKey ( sourceFile.uri) ,
198198 sourceUriToClassFiles.getOrElse(
199- sourceFile.uri,
199+ SourceFileKey ( sourceFile.uri) ,
200200 Seq .empty
201201 ) :+ classFile
202202 )
@@ -358,3 +358,31 @@ private object ClassEntryLookUp {
358358 }
359359 }
360360}
361+
362+ /**
363+ * On a case-insensitive system we need to sanitize all URIs to use them as Map keys.
364+ */
365+ private case class SourceFileKey private (sanitizeUri : URI )
366+
367+ private object SourceFileKey {
368+ private val isCaseSensitiveFileSystem = Properties .isWin || Properties .isMac
369+
370+ def apply (uri : URI ): SourceFileKey = {
371+ val sanitizeUri : URI =
372+ if (isCaseSensitiveFileSystem) {
373+ uri.getScheme match {
374+ case " file" => URI .create(uri.toString.toUpperCase)
375+ case " jar" | " zip" if uri.toString.contains(" !/" ) =>
376+ // The contents of jars are case-sensitive no matter what the filesystem is.
377+ val parts = uri.toString.split(" !/" , 2 ).toSeq
378+ val head = parts.head.toUpperCase()
379+ val tail = parts.tail.mkString(" !/" )
380+ URI .create(s " $head!/ $tail" )
381+ case _ => uri
382+ }
383+ } else uri
384+ new SourceFileKey (sanitizeUri)
385+ }
386+ }
387+
388+ private case class SourceLineKey (sourceFile : SourceFileKey , lineNumber : Int )
0 commit comments