@@ -14,8 +14,23 @@ object Caches:
1414
1515 /** Cache for values of type `V`, associated with keys of type `K`. */
1616 trait Cache [K , V ]:
17+
18+ /** Get the value associated with `key` from the cache, or compute it using
19+ * the by-name parameter `value`.
20+ *
21+ * The value is cached iff `mightContain(key) == true`.
22+ */
1723 def apply (key : K , value : => V ): V
24+
25+ /** Check whether the cache might contain a value for `key`.
26+ *
27+ * `true` means that the cache will cache the value for `key` if requested
28+ * via [[apply ]], not that it has already cached it.
29+ */
30+ def mightContain (key : K ): Boolean
31+
1832 def stats (): CacheStats
33+
1934 override def toString : String =
2035 s " ${this .getClass.getSimpleName}(stats() = ${stats()}) "
2136
@@ -34,6 +49,9 @@ object Caches:
3449 total += 1
3550 value
3651
52+ def mightContain (key : K ): Boolean =
53+ false
54+
3755 def stats (): CacheStats =
3856 CacheStats (total, misses = 0 , uncached = total)
3957
@@ -72,6 +90,9 @@ object Caches:
7290 map.put(key, (stamp, v))
7391 v
7492
93+ def mightContain (key : K ): Boolean =
94+ getStamp(key).isDefined
95+
7596 def stats (): CacheStats =
7697 CacheStats (total, misses, uncached)
7798
@@ -102,6 +123,9 @@ object Caches:
102123 (stamp, value)
103124 )._2
104125
126+ def mightContain (key : K ): Boolean =
127+ getStamp(key).isDefined
128+
105129 def stats (): CacheStats =
106130 CacheStats (total.longValue(), misses.longValue(), uncached.longValue())
107131
@@ -122,27 +146,36 @@ object Caches:
122146 final class FileBasedCache [V ]() extends Cache [AbstractFile , V ]:
123147 private case class FileStamp (lastModified : FileTime , fileKey : Object )
124148
125- private def getFileStamp (abstractFile : AbstractFile ): Option [FileStamp ] =
149+ private def getPath (abstractFile : AbstractFile ): Option [Path ] =
126150 abstractFile.underlyingSource match
127151 case Some (underlyingSource) if underlyingSource ne abstractFile =>
128- getFileStamp (underlyingSource)
152+ getPath (underlyingSource)
129153 case _ =>
130- val javaFile = abstractFile.file
131- if javaFile == null then
132- None
133- else
134- val attrs = Files .readAttributes(javaFile.toPath, classOf [BasicFileAttributes ])
135- val lastModified = attrs.lastModifiedTime()
136- // This can be `null` on some platforms, but that's okay, we just use
137- // the last modified timestamp as our stamp in that case.
138- val fileKey = attrs.fileKey()
139- Some (FileStamp (lastModified, fileKey))
154+ val javaPath = abstractFile.jpath
155+ if javaPath != null then Some (javaPath) else None
156+
157+ private def getFileStamp (abstractFile : AbstractFile ): Option [FileStamp ] =
158+ getPath(abstractFile) match
159+ case Some (path) =>
160+ val attrs = Files .readAttributes(path, classOf [BasicFileAttributes ])
161+ val lastModified = attrs.lastModifiedTime()
162+ // This can be `null` on some platforms, but that's okay, we just use
163+ // the last modified timestamp as our stamp in that case.
164+ val fileKey = attrs.fileKey()
165+ Some (FileStamp (lastModified, fileKey))
166+ case None =>
167+ None
140168
141169 private val underlying = SynchronizedMapCache [AbstractFile , FileStamp , V ](getFileStamp)
142170
143171 def apply (key : AbstractFile , value : => V ): V =
144172 underlying(key, value)
145173
174+ def mightContain (key : AbstractFile ): Boolean =
175+ // We just check that a path exists here to avoi IO. `getFileStamp` will
176+ // return `None` iff `getPath` returns `None`.
177+ getPath(key).isDefined
178+
146179 def stats (): CacheStats =
147180 underlying.stats()
148181
@@ -175,5 +208,8 @@ object Caches:
175208 uncached = baseStats.uncached + uncached.longValue()
176209 )
177210
211+ def mightContain (key : K ): Boolean =
212+ shouldCache(key) && underlying.mightContain(key)
213+
178214 override def toString : String =
179215 s " FilteringCache( ${underlying.toString}, uncached = ${uncached.longValue()}) "
0 commit comments