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

io.kaitai.struct.formats.JavaKSYParser.scala Maven / Gradle / Ivy

package io.kaitai.struct.formats

import java.io._
import java.nio.charset.StandardCharsets
import java.util.{List => JList, Map => JMap}
import io.kaitai.struct.JavaMain.CLIConfig
import io.kaitai.struct.format.{ClassSpec, ClassSpecs}
import io.kaitai.struct.problems.{CompilationProblem, CompilationProblemException, ProblemCoords, ProblemSeverity, YAMLParserError}
import io.kaitai.struct.{Log, Main}
import org.yaml.snakeyaml.constructor.SafeConstructor
import org.yaml.snakeyaml.error.MarkedYAMLException
import org.yaml.snakeyaml.representer.Representer
import org.yaml.snakeyaml.{DumperOptions, LoaderOptions, Yaml}

import scala.collection.JavaConverters._
import scala.concurrent.Await
import scala.concurrent.duration.Duration

object JavaKSYParser {
  def localFileToSpecs(yamlFilename: String, config: CLIConfig): (Option[ClassSpecs], Iterable[CompilationProblem]) = {
    try {
      val firstSpec = fileNameToSpec(yamlFilename)
      val yamlDir = Option(new File(yamlFilename).getParent).getOrElse(".")
      val specs = new JavaClassSpecs(yamlDir, config.importPaths, firstSpec)

      val problems = Await.result(Main.importAndPrecompile(specs, config.runtime), Duration.Inf)
      val gotError = problems.exists(p => p.severity != ProblemSeverity.Warning)
      (if (gotError) None else Some(specs), problems)
    } catch {
      case ex: CompilationProblemException =>
        val problem = ex.problem
        val problemLocalized = if (problem.coords.file.isEmpty) {
          problem.localizedInFile(yamlFilename)
        } else {
          problem
        }
        (None, List(problemLocalized))
    }
  }

  def fileNameToSpec(yamlFilename: String): ClassSpec = {
    Log.fileOps.info(() => s"reading $yamlFilename...")

    // This complex string of classes is due to the fact that Java's
    // default "FileReader" implementation always uses system locale,
    // which screws up encoding on some systems and screws up reading
    // UTF-8 files with BOM
    val fis = new FileInputStream(yamlFilename)
    val isr = new InputStreamReader(fis, StandardCharsets.UTF_8)
    val br = new BufferedReader(isr)
    try {
      val scalaSrc = readerToYaml(br)
      ClassSpec.fromYaml(scalaSrc, Some(yamlFilename))
    } catch {
      case marked: MarkedYAMLException =>
        val mark = marked.getProblemMark
        throw CompilationProblemException(
          YAMLParserError(
            marked.getProblem,
            ProblemCoords(
              Some(yamlFilename),
              None,
              Some(mark.getLine + 1),
              Some(mark.getColumn + 1)
            )
          )
        )
    }
  }

  def getYamlLoader: Yaml = {
    val loaderOptions = new LoaderOptions
    loaderOptions.setAllowDuplicateKeys(false)
    new Yaml(
      new SafeConstructor,
      new Representer,
      new DumperOptions,
      loaderOptions
    )
  }

  def readerToYaml(reader: Reader): Any = {
    yamlJavaToScala(getYamlLoader.load(reader))
  }

  def stringToYaml(data: String): Any = {
    yamlJavaToScala(getYamlLoader.load(data))
  }

  def yamlJavaToScala(src: Any): Any = {
    src match {
      case jlist: JList[AnyRef] =>
        jlist.asScala.toList.map(yamlJavaToScala)
      case jmap: JMap[String, AnyRef] =>
        jmap.asScala.toMap.mapValues(yamlJavaToScala)
      case _: String =>
        src
      case _: Double =>
        src
      case _: Boolean =>
        src
      case javaInt: java.lang.Integer =>
        javaInt.intValue
      case javaLong: java.lang.Long =>
        javaLong.longValue
      case _: java.math.BigInteger =>
        src.toString
      case null =>
        // may be not the very best idea, but these nulls
        // should be handled by real parsing code, i.e. where
        // it tracks tree depth, etc.
        null
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy