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

io.kaitai.struct.precompile.LoadImports.scala Maven / Gradle / Ivy

package io.kaitai.struct.precompile

import io.kaitai.struct.Log
import io.kaitai.struct.format.{ClassSpec, ClassSpecs}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

/**
  * Precompilation stage that manages loading of extra .ksy files requested in
  * `meta/imports` key of initial .ksy file.
  *
  * @param specs collection of [[ClassSpec]] entries to work on
  */
class LoadImports(specs: ClassSpecs) {
  import LoadImports._

  /**
    * Recursively loads and processes all .ksy files referenced in
    * `meta/import` section of given class spec and all nested classes.
    * Starts with a single given class, and recursively processes
    * both classes that will be loaded due to import, and nested classes
    * into given class.
    *
    * @param curClass class spec to start recursive import from
    */
  def processClass(curClass: ClassSpec, workDir: ImportPath): Future[List[ClassSpec]] = {
    Log.importOps.info(() => s".. LoadImports: processing class ${curClass.nameAsStr} (workDir = $workDir)")

    val thisMetaFuture: Future[List[ClassSpec]] =
      Future.sequence(curClass.meta.imports.zipWithIndex.map { case (name, idx) =>
        loadImport(
          name,
          curClass.meta.path ++ List("imports", idx.toString),
          Some(curClass.nameAsStr),
          workDir
        )
      }).map((x) => x.flatten)

    val nestedFuture: Future[Iterable[ClassSpec]] = Future.sequence(curClass.types.map({
      case (_, nestedClass) => processClass(nestedClass, workDir)
    })).map((listOfLists) => listOfLists.flatten)

    Future.sequence(List(thisMetaFuture, nestedFuture)).map((x) => x.flatten)
  }

  private def loadImport(name: String, path: List[String], inFile: Option[String], workDir: ImportPath): Future[List[ClassSpec]] = {
    Log.importOps.info(() => s".. LoadImports: loadImport($name, workDir = $workDir)")

    val impPath = ImportPath.fromString(name)
    val fullPath = ImportPath.add(workDir, impPath)

    val futureSpec = fullPath match {
      case RelativeImportPath(p) =>
        specs.importRelative(p.mkString("/"), path, inFile)
      case AbsoluteImportPath(p) =>
        specs.importAbsolute(p.mkString("/"), path, inFile)
    }

    futureSpec.flatMap { case optSpec =>
      optSpec match {
        case Some(spec) =>
          val specName = spec.name.head
          // Check if we've already had this spec in our ClassSpecs. If we do,
          // don't do anything: we've already processed it and reprocessing it
          // might lead to infinite recursion.
          //
          // In theory, duplicate imports shouldn't be returned at all by
          // import* methods due to caching, but we won't rely on it here.
          if (!specs.contains(specName)) {
            specs(specName) = spec
            processClass(spec, ImportPath.updateWorkDir(workDir, impPath))
          } else {
            Future { List() }
          }
        case None =>
          Future { List() }
      }
    }
  }
}

object LoadImports {
  sealed trait ImportPath {
    def baseDir: ImportPath
  }
  case class RelativeImportPath(path: List[String]) extends ImportPath {
    override def baseDir: ImportPath = RelativeImportPath(path.init)
  }
  case class AbsoluteImportPath(path: List[String]) extends ImportPath {
    override def baseDir: ImportPath = AbsoluteImportPath(path.init)
  }
  val BasePath = RelativeImportPath(List())

  object ImportPath {
    def fromString(s: String): ImportPath = if (s.startsWith("/")) {
      AbsoluteImportPath(s.substring(1).split("/", -1).toList)
    } else {
      RelativeImportPath(s.split("/", -1).toList)
    }

    def add(curWorkDir: ImportPath, newPath: ImportPath): ImportPath = {
      (curWorkDir, newPath) match {
        case (_, AbsoluteImportPath(newPathAbs)) =>
          AbsoluteImportPath(newPathAbs)
        case (RelativeImportPath(curDir), RelativeImportPath(newPathRel)) =>
          RelativeImportPath(curDir ++ newPathRel)
        case (AbsoluteImportPath(curDir), RelativeImportPath(newPathRel)) =>
          AbsoluteImportPath(curDir ++ newPathRel)
      }
    }

    def updateWorkDir(curWorkDir: ImportPath, newPath: ImportPath): ImportPath =
      add(curWorkDir, newPath).baseDir
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy