All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalafix.internal.util.LazySymbolTable.scala Maven / Gradle / Ivy

package scalafix.internal.util

import java.nio.file.Files
import java.util.jar.JarFile
import org.langmeta.internal.io.PathIO
import org.langmeta.io.AbsolutePath
import org.langmeta.io.Classpath
import scala.collection.concurrent.TrieMap
import scala.meta.internal.semanticdb3.Scala.Symbols
import scala.meta.internal.semanticdb3.Scala._
import scala.meta.internal.{semanticdb3 => s}

/**
  * Implementation of SymbolTable that lazily loads symbols on demand.
  *
  * @note Credits to the rsc symbol table implementation
  *       https://github.com/twitter/rsc/blob/c82ce2ee68729e2cbcca7c96a164a303a152a43c/rsc/src/main/scala/rsc/symtab/Loaders.scala
  *       that was a source of inspiration for the implementation of this class.
  * @param mclasspath The classpath to load symbols from. Needs to be pre-processed by metacp,
  *                   see ClasspathOps.toMclasspath in scalafix-cli.
  */
class LazySymbolTable(mclasspath: Classpath) extends SymbolTable {

  // Map from symbols to the paths to their corresponding semanticdb file where they are stored.
  private val notYetLoadedSymbols = TrieMap.empty[String, SemanticdbEntry]
  private val loadedSymbols = TrieMap.empty[String, s.SymbolInformation]

  mclasspath.shallow.foreach(loadSemanticdbIndex)

  override def info(symbol: String): Option[s.SymbolInformation] = {
    var result = loadedSymbols.get(symbol)
    if (result.isEmpty) {
      loadSymbolFromClasspath(symbol)
      result = loadedSymbols.get(symbol)
      if (result.isEmpty && symbol.owner != Symbols.None) {
        info(symbol.owner)
        result = loadedSymbols.get(symbol)
      }
    }
    result
  }

  private def loadSymbolFromClasspath(symbol: String): Unit = {
    val toLoad = notYetLoadedSymbols.get(symbol)
    if (toLoad.isDefined) {
      val in = toLoad.get match {
        case Uncompressed(path, uri) =>
          Files.newInputStream(
            path.resolve("META-INF").resolve("semanticdb").resolve(uri).toNIO)
        case Compressed(jar, entry) =>
          jar.getInputStream(jar.getEntry("META-INF/semanticdb/" + entry))
      }
      val semanticdb =
        try s.TextDocuments.parseFrom(in)
        finally in.close()
      semanticdb.documents.foreach { document =>
        document.symbols.foreach { info =>
          loadedSymbols.put(info.symbol, info)
        }
      }
      notYetLoadedSymbols.remove(symbol)
    }
  }

  private def loadSemanticdbIndex(root: AbsolutePath): Unit = {
    if (root.isDirectory) {
      val indexPath = root.resolve("META-INF").resolve("semanticdb.semanticidx")
      if (indexPath.isFile) {
        val in = Files.newInputStream(indexPath.toNIO)
        val index =
          try s.Index.parseFrom(in)
          finally in.close()
        index.toplevels.foreach { toplevel =>
          notYetLoadedSymbols.put(
            toplevel.symbol,
            Uncompressed(root, toplevel.uri))
        }
      }
    } else if (PathIO.extension(root.toNIO) == "jar") {
      val jar = new JarFile(root.toFile)
      val entry = jar.getEntry("META-INF/semanticdb.semanticidx")
      if (entry != null) {
        val in = jar.getInputStream(entry)
        val index =
          try s.Index.parseFrom(in)
          finally in.close()
        index.toplevels.foreach { toplevel =>
          notYetLoadedSymbols.put(
            toplevel.symbol,
            Compressed(jar, toplevel.uri)
          )
        }
      }
    } else {
      throw new IllegalArgumentException(root.toString())
    }
  }

  private sealed trait SemanticdbEntry
  private case class Uncompressed(root: AbsolutePath, uri: String)
      extends SemanticdbEntry
  private case class Compressed(jarFile: JarFile, entry: String)
      extends SemanticdbEntry
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy