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

sbt.internal.inc.binary.converters.ProtobufReaders.scala Maven / Gradle / Ivy

The newest version!
/*
 * Zinc - The incremental compiler for Scala.
 * Copyright Scala Center, Lightbend, and Mark Harrah
 *
 * Licensed under Apache License 2.0
 * SPDX-License-Identifier: Apache-2.0
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package sbt.internal.inc.binary.converters

import java.nio.file.{ Path, Paths }
import java.util.{ List => JList, Map => JMap, HashMap => JHashMap }
import sbt.internal.inc.Relations.ClassDependencies
import sbt.internal.inc._
import sbt.internal.inc.binary.converters.ProtobufDefaults.EmptyLazyCompanions
import sbt.util.InterfaceUtil
import xsbti.{
  Action,
  DiagnosticCode,
  DiagnosticRelatedInformation,
  Position,
  Problem,
  Severity,
  T2,
  TextEdit,
  UseScope,
  VirtualFileRef,
  WorkspaceEdit,
}
import xsbti.compile.{ CompileOrder, FileHash, MiniOptions, MiniSetup, Output, OutputGroup }
import xsbti.compile.analysis.{ Compilation, ReadMapper, SourceInfo, Stamp }
import sbt.internal.inc.binary.converters.ProtobufDefaults.Feedback.StringToException
import sbt.internal.inc.binary.converters.ProtobufDefaults.Feedback.{ Readers => ReadersFeedback }
import sbt.internal.inc.binary.converters.ProtobufDefaults.{ Classes, ReadersConstants }
import sbt.internal.util.Relation

import scala.collection.JavaConverters._
import xsbti.api._
import ProtobufDefaults.{ MissingInt, MissingString }

final class ProtobufReaders(mapper: ReadMapper, currentVersion: Schema.Version) {
  def fromPathString(path: String): Path = Paths.get(path)
  def fromPathStringV(path: String): VirtualFileRef = {
    VirtualFileRef.of(path)
  }

  def fromStampType(stampType: Schema.Stamps.StampType): Stamp = {
    import sbt.internal.inc.{ EmptyStamp, LastModified, Hash }
    stampType.getTypeCase match {
      case Schema.Stamps.StampType.TypeCase.TYPE_NOT_SET => EmptyStamp
      case Schema.Stamps.StampType.TypeCase.FARMHASH =>
        FarmHash.fromLong(stampType.getFarmHash.getHash)
      case Schema.Stamps.StampType.TypeCase.HASH =>
        Hash.unsafeFromString(stampType.getHash.getHash) // fair assumption
      case Schema.Stamps.StampType.TypeCase.LASTMODIFIED =>
        new LastModified(stampType.getLastModified.getMillis)
    }
  }

  def fromStamps(stamps: Schema.Stamps): Stamps = {
    // Note that boilerplate here is inteded, abstraction is expensive
    def fromBinarySchemaMap(
        stamps: JMap[String, Schema.Stamps.StampType]
    ): Map[VirtualFileRef, Stamp] = {
      stamps.asScala.iterator.map {
        case (path, schemaStamp) =>
          val file = fromPathStringV(path)
          val newFile = mapper.mapBinaryFile(file)
          val stamp = fromStampType(schemaStamp)
          val newStamp = mapper.mapBinaryStamp(newFile, stamp)
          newFile -> newStamp
      }.toMap
    }

    def fromSourceSchemaMap(
        stamps: JMap[String, Schema.Stamps.StampType]
    ): Map[VirtualFileRef, Stamp] = {
      stamps.asScala.iterator.map {
        case (path, schemaStamp) =>
          val file = fromPathStringV(path)
          val newFile = mapper.mapSourceFile(file)
          val stamp = fromStampType(schemaStamp)
          val newStamp = mapper.mapSourceStamp(newFile, stamp)
          newFile -> newStamp
      }.toMap
    }

    def fromProductSchemaMap(
        stamps: JMap[String, Schema.Stamps.StampType]
    ): Map[VirtualFileRef, Stamp] = {
      stamps.asScala.iterator.map {
        case (path, schemaStamp) =>
          val file = fromPathStringV(path)
          val newFile = mapper.mapProductFile(file)
          val stamp = fromStampType(schemaStamp)
          val newStamp = mapper.mapProductStamp(newFile, stamp)
          newFile -> newStamp
      }.toMap
    }

    val libraries = fromBinarySchemaMap(stamps.getBinaryStampsMap)
    val sources = fromSourceSchemaMap(stamps.getSourceStampsMap)
    val products = fromProductSchemaMap(stamps.getProductStampsMap)
    Stamps(
      products = products,
      sources = sources,
      libraries = libraries,
    )
  }

  def fromOutputGroup(outputGroup: Schema.OutputGroup): OutputGroup = {
    val sourcePath = fromPathString(outputGroup.getSourcePath)
    val sourceDir = mapper.mapSourceDir(sourcePath)
    val targetPath = fromPathString(outputGroup.getTargetPath)
    val targetDir = mapper.mapOutputDir(targetPath)
    CompileOutput.outputGroup(sourceDir, targetDir)
  }

  def fromCompilationOutput(c: Schema.Compilation): Output = {
    import Schema.Compilation.{ OutputCase => CompilationOutput }
    c.getOutputCase match {
      case CompilationOutput.SINGLEOUTPUT =>
        val single = c.getSingleOutput
        val target = fromPathString(single.getTarget)
        if (target == Analysis.dummyOutputPath) CompileOutput.empty
        else {
          val outputDir = mapper.mapOutputDir(target)
          CompileOutput(outputDir)
        }
      case CompilationOutput.MULTIPLEOUTPUT =>
        val multiple = c.getMultipleOutput
        val groups = multiple.getOutputGroupsList.asScala.iterator.map(fromOutputGroup).toArray
        CompileOutput(groups)
      case CompilationOutput.OUTPUT_NOT_SET =>
        ReadersFeedback.ExpectedOutputInCompilationOutput.!!
    }
  }

  def fromCompilation(compilation: Schema.Compilation): Compilation = {
    val output = fromCompilationOutput(compilation)
    new sbt.internal.inc.Compilation(compilation.getStartTimeMillis, output)
  }

  def fromCompilations(compilations0: Schema.Compilations): Compilations = {
    val compilations = compilations0.getCompilationsList.asScala.iterator.map(fromCompilation)
    val castedCompilations = (compilations.map { case c: sbt.internal.inc.Compilation => c }).toSeq
    Compilations.of(castedCompilations)
  }

  def fromPosition(position: Schema.Position): Position = {
    InterfaceUtil.position(
      line0 = fromInt(position.getLine),
      content = position.getLineContent,
      offset0 = fromInt(position.getOffset),
      pointer0 = fromInt(position.getPointer),
      pointerSpace0 = fromString(position.getPointerSpace),
      sourcePath0 = fromString(position.getSourcePath),
      sourceFile0 = fromString(position.getSourceFilepath).map(fromPathString).map(_.toFile),
      startOffset0 = fromInt(position.getStartOffset),
      endOffset0 = fromInt(position.getEndOffset),
      startLine0 = fromInt(position.getStartLine),
      startColumn0 = fromInt(position.getStartColumn),
      endLine0 = fromInt(position.getEndLine),
      endColumn0 = fromInt(position.getEndColumn),
    )
  }

  def fromSeverity(severity: Schema.Severity, id: Int): Severity = {
    severity match {
      case Schema.Severity.INFO         => Severity.Info
      case Schema.Severity.WARN         => Severity.Warn
      case Schema.Severity.ERROR        => Severity.Error
      case Schema.Severity.UNRECOGNIZED => ReadersFeedback.unrecognizedSeverity(id).!!
    }
  }

  private def fromString(value: String): Option[String] =
    if (value == MissingString) None else Some(value)

  private def fromInt(value: Int): Option[Integer] =
    if (value == MissingInt) None else Some(value)

  private def fromDiagnosticCode(diagnosticCode: Schema.DiagnosticCode): DiagnosticCode =
    DiagnosticsUtil.diagnosticCode(
      code = diagnosticCode.getCode(),
      explanation = fromString(diagnosticCode.getExplanation()),
    )

  private def fromDiagnosticRelatedInformation(info: Schema.DiagnosticRelatedInformation)
      : DiagnosticRelatedInformation =
    DiagnosticsUtil.diagnosticRelatedInformation(
      position = fromPosition(info.getPosition()),
      message = info.getMessage(),
    )

  private def fromAction(action: Schema.Action): Action =
    InterfaceUtil.action(
      title = action.getTitle(),
      description = fromString(action.getDescription()),
      edit = fromWorkspaceEdit(action.getEdit()),
    )

  private def fromWorkspaceEdit(edit: Schema.WorkspaceEdit): WorkspaceEdit =
    InterfaceUtil.workspaceEdit(
      changes = edit.getChangesList().asScala.iterator.map(fromTextEdit).toList
    )

  private def fromTextEdit(edit: Schema.TextEdit): TextEdit =
    InterfaceUtil.textEdit(
      position = fromPosition(edit.getPosition()),
      newText = edit.getNewText(),
    )

  def fromProblem(problem: Schema.Problem): Problem = {
    val category = problem.getCategory
    val message = problem.getMessage
    val severity = fromSeverity(problem.getSeverity, problem.getSeverityValue)
    val position =
      if (problem.hasPosition) fromPosition(problem.getPosition)
      else ReadersFeedback.ExpectedPositionInProblem.!!
    val rendered = fromString(problem.getRendered)
    val diagnosticCode =
      if (problem.hasDiagnosticCode) Some(fromDiagnosticCode(problem.getDiagnosticCode()))
      else None
    val infos = problem.getDiagnosticRelatedInformationList().asScala.iterator
      .map(fromDiagnosticRelatedInformation).toList
    val actions = problem.getActionsList().asScala.iterator
      .map(fromAction).toList
    InterfaceUtil.problem(
      cat = category,
      pos = position,
      msg = message,
      sev = severity,
      rendered = rendered,
      diagnosticCode = diagnosticCode,
      diagnosticRelatedInformation = infos,
      actions = actions,
    )
  }

  def fromSourceInfo(sourceInfo: Schema.SourceInfo): SourceInfo = {
    val mainClasses = sourceInfo.getMainClassesList.asScala.toSeq
    val reportedProblems =
      sourceInfo.getReportedProblemsList.asScala.iterator.map(fromProblem).toSeq
    val unreportedProblems =
      sourceInfo.getUnreportedProblemsList.asScala.iterator.map(fromProblem).toSeq
    SourceInfos.makeInfo(
      reported = reportedProblems,
      unreported = unreportedProblems,
      mainClasses = mainClasses
    )
  }

  def fromSourceInfos(sourceInfos0: Schema.SourceInfos): SourceInfos = {
    val sourceInfos = sourceInfos0.getSourceInfosMap.asScala.iterator.map {
      case (path, value) =>
        val file = mapper.mapSourceFile(fromPathStringV(path))
        val sourceInfo = fromSourceInfo(value)
        file -> sourceInfo
    }
    SourceInfos.of(sourceInfos.toMap)
  }

  def fromClasspathFileHash(fileHash: Schema.FileHash): FileHash = {
    val hash = fileHash.getHash
    val classpathEntry = fromPathString(fileHash.getPath)
    val newClasspathEntry = mapper.mapClasspathEntry(classpathEntry)
    FileHash.of(newClasspathEntry, hash)
  }

  def fromMiniOptions(miniOptions: Schema.MiniOptions): MiniOptions = {
    val classpathHash =
      miniOptions.getClasspathHashList.asScala.iterator.map(fromClasspathFileHash).toArray
    val javacOptions =
      miniOptions.getJavacOptionsList.asScala.iterator.map(mapper.mapJavacOption).toArray
    val scalacOptions =
      miniOptions.getScalacOptionsList.asScala.iterator.map(mapper.mapScalacOption).toArray
    MiniOptions.of(classpathHash, scalacOptions, javacOptions)
  }

  def fromCompileOrder(compileOrder: Schema.CompileOrder, id: Int): CompileOrder = {
    compileOrder match {
      case Schema.CompileOrder.MIXED         => CompileOrder.Mixed
      case Schema.CompileOrder.JAVATHENSCALA => CompileOrder.JavaThenScala
      case Schema.CompileOrder.SCALATHENJAVA => CompileOrder.ScalaThenJava
      case Schema.CompileOrder.UNRECOGNIZED  => ReadersFeedback.unrecognizedOrder(id).!!
    }
  }

  def fromStringTuple(tuple: Schema.Tuple): T2[String, String] = {
    InterfaceUtil.t2(tuple.getFirst -> tuple.getSecond)
  }

  def fromMiniSetupOutput(miniSetup: Schema.MiniSetup): Output = {
    import Schema.MiniSetup.{ OutputCase => MiniSetupOutput }
    miniSetup.getOutputCase match {
      case MiniSetupOutput.SINGLEOUTPUT =>
        val single = miniSetup.getSingleOutput
        val target = fromPathString(single.getTarget)
        if (target == Analysis.dummyOutputPath) CompileOutput.empty
        else {
          val outputDir = mapper.mapOutputDir(target)
          CompileOutput(outputDir)
        }
      case MiniSetupOutput.MULTIPLEOUTPUT =>
        val multiple = miniSetup.getMultipleOutput
        val groups = multiple.getOutputGroupsList.asScala.iterator.map(fromOutputGroup).toArray
        CompileOutput(groups)
      case MiniSetupOutput.OUTPUT_NOT_SET =>
        ReadersFeedback.ExpectedOutputInCompilationOutput.!!
    }
  }

  def fromMiniSetup(miniSetup: Schema.MiniSetup): MiniSetup = {
    val output = fromMiniSetupOutput(miniSetup)
    val miniOptions =
      if (miniSetup.hasMiniOptions) fromMiniOptions(miniSetup.getMiniOptions)
      else ReadersFeedback.ExpectedMiniOptionsInSetup.!!
    val compilerVersion = miniSetup.getCompilerVersion
    val compileOrder = fromCompileOrder(miniSetup.getCompileOrder, miniSetup.getCompileOrderValue)
    val storeApis = miniSetup.getStoreApis
    val extra = miniSetup.getExtraList.asScala.iterator.map(fromStringTuple).toArray
    val original =
      MiniSetup.of(
        output, // note this is a dummy value
        miniOptions,
        compilerVersion,
        compileOrder,
        storeApis,
        extra
      )
    mapper.mapMiniSetup(original)
  }

  implicit class EfficientTraverse[T](seq: JList[T]) {
    def toZincArray[R <: AnyRef: scala.reflect.ClassTag](f: T => R): Array[R] = {
      seq.stream().map[R](x => f(x)).toArray[R](new Array[R](_))
    }
  }

  implicit class OptionReader[T](option: Option[T]) {
    def read[R](from: T => R, errorMessage: => String): R =
      option.fold(errorMessage.!!)(from)
  }

  def fromPath(path: Schema.Path): xsbti.api.Path = {
    def fromPathComponent(pathComponent: Schema.Path.PathComponent): PathComponent = {
      import ReadersFeedback.ExpectedPathInSuper
      import Schema.Path.{ PathComponent => SchemaPath }
      import SchemaPath.{ ComponentCase => SchemaComponent }
      import Classes.{ Component, PathComponent }
      pathComponent.getComponentCase match {
        case SchemaComponent.ID =>
          val c = pathComponent.getId
          Id.of(c.getId)
        case SchemaComponent.SUPER =>
          val c = pathComponent.getSuper
          val q =
            if (c.hasQualifier) fromPath(c.getQualifier)
            else ExpectedPathInSuper.!!
          Super.of(q)
        case SchemaComponent.THIS => ReadersConstants.This
        case SchemaComponent.COMPONENT_NOT_SET =>
          ReadersFeedback.expected(Component, PathComponent).!!
      }
    }
    val components = path.getComponentsList.toZincArray(fromPathComponent)
    xsbti.api.Path.of(components)
  }

  def fromAnnotation(annotation: Schema.Annotation): Annotation = {
    def fromAnnotationArgument(argument: Schema.AnnotationArgument): AnnotationArgument = {
      val name = argument.getName.intern()
      val value = argument.getValue
      AnnotationArgument.of(name, value)
    }

    val arguments = annotation.getArgumentsList.toZincArray(fromAnnotationArgument)
    val b =
      if (annotation.hasBase) Some(annotation.getBase)
      else None
    val base = b.read(fromType, ReadersFeedback.expectedBaseIn(Classes.Annotation))
    Annotation.of(base, arguments)
  }

  def fromStructure(tpe: Schema.Type.Structure): Structure = {
    def `lazy`[T](value: T): Lazy[T] = SafeLazyProxy.strict(value)
    val parents = `lazy`(tpe.getParentsList.toZincArray(fromType))
    val declared = `lazy`(tpe.getDeclaredList.toZincArray(fromClassDefinition))
    val inherited = `lazy`(tpe.getInheritedList.toZincArray(fromClassDefinition))
    Structure.of(parents, declared, inherited)
  }

  def fromType(`type`: Schema.Type): Type = {
    import ReadersFeedback.expectedBaseIn
    def fromParameterRef(tpe: Schema.Type.ParameterRef): ParameterRef = {
      ParameterRef.of(tpe.getId)
    }

    def fromParameterized(tpe: Schema.Type.Parameterized): Parameterized = {
      val baseType =
        if (tpe.hasBaseType) fromType(tpe.getBaseType)
        else expectedBaseIn(Classes.Parameterized).!!
      val typeArguments = tpe.getTypeArgumentsList.toZincArray(fromType)
      Parameterized.of(baseType, typeArguments)
    }

    def fromPolymorphic(tpe: Schema.Type.Polymorphic): Polymorphic = {
      val baseType =
        if (tpe.hasBaseType) fromType(tpe.getBaseType)
        else expectedBaseIn(Classes.Polymorphic).!!
      val typeParameters = tpe.getTypeParametersList.toZincArray(fromTypeParameter)
      Polymorphic.of(baseType, typeParameters)
    }

    def fromConstant(tpe: Schema.Type.Constant): Constant = {
      val baseType =
        if (tpe.hasBaseType) fromType(tpe.getBaseType)
        else expectedBaseIn(Classes.Constant).!!
      val value = tpe.getValue
      Constant.of(baseType, value)
    }

    def fromExistential(tpe: Schema.Type.Existential): Existential = {
      val b =
        if (tpe.hasBaseType) Some(tpe.getBaseType)
        else None
      val baseType = b.read(fromType, expectedBaseIn(Classes.Existential))
      val clause = tpe.getClauseList.toZincArray(fromTypeParameter)
      Existential.of(baseType, clause)
    }

    def fromSingleton(tpe: Schema.Type.Singleton): Singleton = {
      val path =
        if (tpe.hasPath) fromPath(tpe.getPath)
        else ReadersFeedback.ExpectedPathInSingleton.!!
      Singleton.of(path)
    }

    def fromProjection(tpe: Schema.Type.Projection): Projection = {
      val id = tpe.getId
      val prefix =
        if (tpe.hasPrefix) fromType(tpe.getPrefix)
        else ReadersFeedback.ExpectedPrefixInProjection.!!
      Projection.of(prefix, id)
    }

    def fromAnnotated(tpe: Schema.Type.Annotated): Annotated = {
      val baseType =
        if (tpe.hasBaseType) fromType(tpe.getBaseType)
        else expectedBaseIn(Classes.Annotated).!!
      val annotations = tpe.getAnnotationsList.toZincArray(fromAnnotation)
      Annotated.of(baseType, annotations)
    }
    import Schema.Type.ValueCase
    `type`.getValueCase match {
      case ValueCase.PARAMETERREF  => fromParameterRef(`type`.getParameterRef)
      case ValueCase.PARAMETERIZED => fromParameterized(`type`.getParameterized)
      case ValueCase.STRUCTURE     => fromStructure(`type`.getStructure)
      case ValueCase.POLYMORPHIC   => fromPolymorphic(`type`.getPolymorphic)
      case ValueCase.CONSTANT      => fromConstant(`type`.getConstant)
      case ValueCase.EXISTENTIAL   => fromExistential(`type`.getExistential)
      case ValueCase.SINGLETON     => fromSingleton(`type`.getSingleton)
      case ValueCase.PROJECTION    => fromProjection(`type`.getProjection)
      case ValueCase.ANNOTATED     => fromAnnotated(`type`.getAnnotated)
      case ValueCase.EMPTYTYPE     => ReadersConstants.EmptyType
      case ValueCase.VALUE_NOT_SET => ReadersFeedback.ExpectedNonEmptyType.!!
    }
  }

  def fromModifiers(modifiers: Schema.Modifiers): Modifiers =
    InternalApiProxy.Modifiers(modifiers.getFlags)

  def fromAccess(access: Schema.Access): Access = {
    def fromQualifier(qualifier: Schema.Qualifier): Qualifier = {
      import Schema.Qualifier.{ TypeCase => QualifierType }
      qualifier.getTypeCase match {
        case QualifierType.IDQUALIFIER =>
          val q = qualifier.getIdQualifier
          IdQualifier.of(q.getValue)
        case QualifierType.THISQUALIFIER => ReadersConstants.ThisQualifier
        case QualifierType.UNQUALIFIED   => ReadersConstants.Unqualified
        case QualifierType.TYPE_NOT_SET  => ReadersFeedback.ExpectedNonEmptyQualifier.!!
      }
    }

    def readQualifier(qualifier: Option[Schema.Qualifier]): Qualifier =
      qualifier.read(fromQualifier, ReadersFeedback.ExpectedQualifierInAccess)

    access.getTypeCase match {
      case Schema.Access.TypeCase.PUBLIC => ReadersConstants.Public
      case Schema.Access.TypeCase.PROTECTED =>
        val a = access.getProtected
        Protected.of(readQualifier(if (a.hasQualifier) Some(a.getQualifier) else None))
      case Schema.Access.TypeCase.PRIVATE =>
        val a = access.getPrivate
        Private.of(readQualifier(if (a.hasQualifier) Some(a.getQualifier) else None))
      case Schema.Access.TypeCase.TYPE_NOT_SET => ReadersFeedback.ExpectedValidAccessType.!!
    }
  }

  def fromDefinitionType(definitionType: Schema.DefinitionType): DefinitionType = {
    definitionType match {
      case Schema.DefinitionType.CLASSDEF      => DefinitionType.ClassDef
      case Schema.DefinitionType.MODULE        => DefinitionType.Module
      case Schema.DefinitionType.TRAIT         => DefinitionType.Trait
      case Schema.DefinitionType.PACKAGEMODULE => DefinitionType.PackageModule
      case Schema.DefinitionType.UNRECOGNIZED  => ReadersFeedback.UnrecognizedDefinitionType.!!
    }
  }

  def fromClassDefinition(classDefinition: Schema.ClassDefinition): ClassDefinition = {
    import ReadersFeedback.{ MissingModifiersInDef, MissingAccessInDef, expectedTypeIn }
    import ReadersFeedback.{
      ExpectedReturnTypeInDef,
      ExpectedLowerBoundInTypeDeclaration,
      ExpectedUpperBoundInTypeDeclaration
    }

    val name = classDefinition.getName.intern()
    val access =
      if (classDefinition.hasAccess) fromAccess(classDefinition.getAccess)
      else MissingAccessInDef.!!
    val modifiers =
      if (classDefinition.hasModifiers) fromModifiers(classDefinition.getModifiers)
      else MissingModifiersInDef.!!
    val annotations = classDefinition.getAnnotationsList.toZincArray(fromAnnotation)

    def fromParameterList(parameterList: Schema.ParameterList): ParameterList = {
      def fromMethodParameter(methodParameter: Schema.MethodParameter): MethodParameter = {
        def fromParameterModifier(modifier: Schema.ParameterModifier): ParameterModifier = {
          modifier match {
            case Schema.ParameterModifier.PLAIN    => ParameterModifier.Plain
            case Schema.ParameterModifier.BYNAME   => ParameterModifier.ByName
            case Schema.ParameterModifier.REPEATED => ParameterModifier.Repeated
            case Schema.ParameterModifier.UNRECOGNIZED =>
              ReadersFeedback.UnrecognizedParamModifier.!!
          }
        }
        val name = methodParameter.getName.intern()
        val hasDefault = methodParameter.getHasDefault
        val `type` =
          if (methodParameter.hasType) fromType(methodParameter.getType)
          else expectedTypeIn(Classes.MethodParameter).!!
        val modifier = fromParameterModifier(methodParameter.getModifier)
        MethodParameter.of(name, `type`, hasDefault, modifier)
      }

      val isImplicit = parameterList.getIsImplicit
      val parameters = parameterList.getParametersList.toZincArray(fromMethodParameter)
      ParameterList.of(parameters, isImplicit)
    }

    def fromClassLikeDef(defDef: Schema.ClassDefinition.ClassLikeDef): ClassLikeDef = {
      val definitionType = fromDefinitionType(defDef.getDefinitionType)
      val typeParameters = defDef.getTypeParametersList.toZincArray(fromTypeParameter)
      ClassLikeDef.of(name, access, modifiers, annotations, typeParameters, definitionType)
    }

    def fromDefDef(defDef: Schema.ClassDefinition.Def): Def = {
      val returnType =
        if (defDef.hasReturnType) fromType(defDef.getReturnType)
        else ExpectedReturnTypeInDef.!!
      val typeParameters = defDef.getTypeParametersList.toZincArray(fromTypeParameter)
      val valueParameters = defDef.getValueParametersList.toZincArray(fromParameterList)
      Def.of(name, access, modifiers, annotations, typeParameters, valueParameters, returnType)
    }

    def fromValDef(valDef: Schema.ClassDefinition.Val): Val = {
      val tpe =
        if (valDef.hasType) fromType(valDef.getType)
        else expectedTypeIn(Classes.Val).!!
      Val.of(name, access, modifiers, annotations, tpe)
    }

    def fromVarDef(varDef: Schema.ClassDefinition.Var): Var = {
      val tpe =
        if (varDef.hasType) fromType(varDef.getType)
        else expectedTypeIn(Classes.Var).!!
      Var.of(name, access, modifiers, annotations, tpe)
    }

    def fromTypeAlias(typeAlias: Schema.ClassDefinition.TypeAlias): TypeAlias = {
      val tpe =
        if (typeAlias.hasType) fromType(typeAlias.getType)
        else expectedTypeIn(Classes.TypeAlias).!!
      val typeParameters = typeAlias.getTypeParametersList.toZincArray(fromTypeParameter)
      TypeAlias.of(name, access, modifiers, annotations, typeParameters, tpe)
    }

    def fromTypeDeclaration(decl: Schema.ClassDefinition.TypeDeclaration): TypeDeclaration = {
      val lowerBound =
        if (decl.hasLowerBound) fromType(decl.getLowerBound)
        else ExpectedLowerBoundInTypeDeclaration.!!
      val upperBound =
        if (decl.hasUpperBound) fromType(decl.getUpperBound)
        else ExpectedUpperBoundInTypeDeclaration.!!
      val typeParams = decl.getTypeParametersList.toZincArray(fromTypeParameter)
      TypeDeclaration.of(name, access, modifiers, annotations, typeParams, lowerBound, upperBound)
    }

    import Schema.ClassDefinition.{ ExtraCase => DefType }
    classDefinition.getExtraCase match {
      case DefType.CLASSLIKEDEF    => fromClassLikeDef(classDefinition.getClassLikeDef)
      case DefType.DEFDEF          => fromDefDef(classDefinition.getDefDef)
      case DefType.VALDEF          => fromValDef(classDefinition.getValDef)
      case DefType.VARDEF          => fromVarDef(classDefinition.getVarDef)
      case DefType.TYPEALIAS       => fromTypeAlias(classDefinition.getTypeAlias)
      case DefType.TYPEDECLARATION => fromTypeDeclaration(classDefinition.getTypeDeclaration)
      case DefType.EXTRA_NOT_SET   => ReadersFeedback.ExpectedNonEmptyDefType.!!
    }
  }

  def fromTypeParameter(typeParameter: Schema.TypeParameter): TypeParameter = {
    def fromVariance(variance: Schema.Variance): Variance = {
      variance match {
        case Schema.Variance.INVARIANT     => Variance.Invariant
        case Schema.Variance.COVARIANT     => Variance.Covariant
        case Schema.Variance.CONTRAVARIANT => Variance.Contravariant
        case Schema.Variance.UNRECOGNIZED  => ReadersFeedback.UnrecognizedVariance.!!
      }
    }

    import ReadersFeedback.{ ExpectedLowerBoundInTypeParameter, ExpectedUpperBoundInTypeParameter }
    val id = typeParameter.getId
    val annotations = typeParameter.getAnnotationsList.toZincArray(fromAnnotation)
    val typeParameters = typeParameter.getTypeParametersList.toZincArray(fromTypeParameter)
    val variance = fromVariance(typeParameter.getVariance)
    val lowerBound =
      if (typeParameter.hasLowerBound) fromType(typeParameter.getLowerBound)
      else ExpectedLowerBoundInTypeParameter.!!
    val upperBound =
      if (typeParameter.hasUpperBound) fromType(typeParameter.getUpperBound)
      else ExpectedUpperBoundInTypeParameter.!!
    TypeParameter.of(id, annotations, typeParameters, variance, lowerBound, upperBound)
  }

  def fromClassLike(classLike: Schema.ClassLike): ClassLike = {
    def expectedMsg(msg: String) = ReadersFeedback.expected(msg, Classes.ClassLike)
    def expected(clazz: Class[?]) = expectedMsg(clazz.getName)
    val name = classLike.getName.intern()
    val access =
      if (classLike.hasAccess) fromAccess(classLike.getAccess)
      else expected(Classes.Access).!!
    val modifiers =
      if (classLike.hasModifiers) fromModifiers(classLike.getModifiers)
      else expected(Classes.Modifiers).!!
    val annotations = classLike.getAnnotationsList.toZincArray(fromAnnotation)

    import SafeLazyProxy.{ strict => mkLazy }
    val definitionType = fromDefinitionType(classLike.getDefinitionType)
    val selfType = mkLazy(
      if (classLike.hasSelfType) fromType(classLike.getSelfType) else expectedMsg("self type").!!
    )
    val structure = mkLazy(
      if (classLike.hasStructure) fromStructure(classLike.getStructure)
      else expected(Classes.Structure).!!
    )
    val savedAnnotations = classLike.getSavedAnnotationsList.asScala.toArray
    val childrenOfSealedClass = classLike.getChildrenOfSealedClassList.toZincArray(fromType)
    val topLevel = classLike.getTopLevel
    val typeParameters = classLike.getTypeParametersList.toZincArray(fromTypeParameter)
    ClassLike.of(
      name,
      access,
      modifiers,
      annotations,
      definitionType,
      selfType,
      structure,
      savedAnnotations,
      childrenOfSealedClass,
      topLevel,
      typeParameters
    )
  }

  def fromUseScope(useScope: Schema.UseScope, id: Int): UseScope = {
    useScope match {
      case Schema.UseScope.DEFAULT      => UseScope.Default
      case Schema.UseScope.IMPLICIT     => UseScope.Implicit
      case Schema.UseScope.PATMAT       => UseScope.PatMatTarget
      case Schema.UseScope.UNRECOGNIZED => ReadersFeedback.unrecognizedUseScope(id).!!
    }
  }

  def fromAnalyzedClass(
      shouldStoreApis: Boolean,
      stringTable: StringTable
  )(analyzedClass: Schema.AnalyzedClass): AnalyzedClass = {
    def fromCompanions(companions: Schema.Companions): Companions = {
      def expected(msg: String) = ReadersFeedback.expected(msg, Classes.Companions)
      val classApi =
        if (companions.hasClassApi) fromClassLike(companions.getClassApi)
        else expected("class api").!!
      val objectApi =
        if (companions.hasObjectApi) fromClassLike(companions.getObjectApi)
        else expected("object api").!!
      Companions.of(classApi, objectApi)
    }

    def fromNameHash(nameHash: Schema.NameHash): NameHash = {
      val name = stringTable.lookupOrEnter(nameHash.getName)
      val hash = nameHash.getHash
      val scope = fromUseScope(nameHash.getScope, nameHash.getScopeValue)
      NameHash.of(name, scope, hash)
    }

    import SafeLazyProxy.{ strict => mkLazy }
    import ReadersFeedback.ExpectedCompanionsInAnalyzedClass
    val compilationTs = analyzedClass.getCompilationTimestamp
    val name = analyzedClass.getName.intern()
    val api =
      if (!shouldStoreApis) EmptyLazyCompanions
      else
        mkLazy(
          if (analyzedClass.hasApi) fromCompanions(analyzedClass.getApi)
          else ExpectedCompanionsInAnalyzedClass.!!
        )

    val apiHash = analyzedClass.getApiHash
    // Default to 0 to avoid issues when comparing hashes from two different analysis formats
    val extraHash = if (currentVersion == Schema.Version.V1) 0 else analyzedClass.getExtraHash
    val nameHashes = analyzedClass.getNameHashesList.toZincArray(fromNameHash)
    val hasMacro = analyzedClass.getHasMacro
    val provenance = analyzedClass.getProvenance.intern
    AnalyzedClass.of(compilationTs, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance)
  }

  private final val stringId = identity[String] _

  private final val stringToSource = (path: String) => mapper.mapSourceFile(fromPathStringV(path))
  private final val stringToLibrary = (path: String) => mapper.mapBinaryFile(fromPathStringV(path))
  private final val stringToProd = (path: String) => mapper.mapProductFile(fromPathStringV(path))

  def fromRelations(relations: Schema.Relations): Relations = {

    def fromMap[K, V](
        map: java.util.Map[String, Schema.Values],
        fk: String => K,
        fv: String => V
    ): Relation[K, V] = {
      val builder = new RelationBuilder[K, V]
      for ((kString, vs) <- map.asScala) {
        if (!vs.getValuesList.isEmpty) {
          val k = fk(kString)
          for (vString <- vs.getValuesList.asScala) {
            builder(k) = fv(vString)
          }
        }
      }
      builder.result()
    }

    def fromClassDependencies(classDependencies: Schema.ClassDependencies): ClassDependencies = {
      val internal = fromMap(classDependencies.getInternalMap, stringId, stringId)
      val external = fromMap(classDependencies.getExternalMap, stringId, stringId)
      new ClassDependencies(internal, external)
    }

    def expected(msg: String) = ReadersFeedback.expected(msg, Classes.Relations)

    val srcProd = fromMap(relations.getSrcProdMap, stringToSource, stringToProd)
    val libraryDep = fromMap(relations.getLibraryDepMap, stringToSource, stringToLibrary)
    val libraryClassName =
      fromMap(relations.getLibraryClassNameMap, stringToLibrary, stringId)
    val memberRef =
      if (relations.hasMemberRef) fromClassDependencies(relations.getMemberRef)
      else expected("member refs").!!
    val inheritance =
      if (relations.hasInheritance) fromClassDependencies(relations.getInheritance)
      else expected("inheritance").!!
    val localInheritance =
      if (relations.hasLocalInheritance) fromClassDependencies(relations.getLocalInheritance)
      else expected("local inheritance").!!
    val macroExpansion =
      if (relations.hasMacroExpansion) fromClassDependencies(relations.getMacroExpansion)
      else new ClassDependencies(Relation.empty, Relation.empty)
    val classes = fromMap(relations.getClassesMap, stringToSource, stringId)
    val productClassName =
      fromMap(relations.getProductClassNameMap, stringId, stringId)
    val names = UsedNames.fromJavaMap(relations.getNamesMap)
    val internal = InternalDependencies(
      Map(
        DependencyContext.DependencyByMemberRef -> memberRef.internal,
        DependencyContext.DependencyByInheritance -> inheritance.internal,
        DependencyContext.LocalDependencyByInheritance -> localInheritance.internal,
        DependencyContext.DependencyByMacroExpansion -> macroExpansion.internal,
      )
    )
    val external = ExternalDependencies(
      Map(
        DependencyContext.DependencyByMemberRef -> memberRef.external,
        DependencyContext.DependencyByInheritance -> inheritance.external,
        DependencyContext.LocalDependencyByInheritance -> localInheritance.external,
        DependencyContext.DependencyByMacroExpansion -> macroExpansion.external,
      )
    )
    Relations.make(
      srcProd,
      libraryDep,
      libraryClassName,
      internal,
      external,
      classes,
      names,
      productClassName
    )
  }

  def fromApis(shouldStoreApis: Boolean)(apis: Schema.APIs): APIs = {
    val stringTable = new StringTable
    val internal =
      apis.getInternalMap.asScala.iterator.map {
        case (k, v) => k -> fromAnalyzedClass(shouldStoreApis, stringTable)(v)
      }.toMap
    val external =
      apis.getExternalMap.asScala.iterator.map {
        case (k, v) => k -> fromAnalyzedClass(shouldStoreApis, stringTable)(v)
      }.toMap
    APIs(internal = internal, external = external)
  }

  def fromApisFile(apisFile: Schema.APIsFile, shouldStoreApis: Boolean): (APIs, Schema.Version) = {
    val apis =
      if (apisFile.hasApis) fromApis(shouldStoreApis)(apisFile.getApis)
      else ReadersFeedback.ExpectedApisInApisFile.!!
    val version = apisFile.getVersion
    apis -> version
  }

  def fromAnalysis(analysis: Schema.Analysis): Analysis = {
    def expected(clazz: Class[?]) = ReadersFeedback.expected(clazz, Classes.Analysis)
    val stamps =
      if (analysis.hasStamps) fromStamps(analysis.getStamps)
      else expected(Classes.Stamps).!!
    val relations =
      if (analysis.hasRelations) fromRelations(analysis.getRelations)
      else expected(Classes.Relations).!!
    val sourceInfos =
      if (analysis.hasSourceInfos) fromSourceInfos(analysis.getSourceInfos)
      else expected(Classes.SourceInfos).!!
    val compilations =
      if (analysis.hasCompilations) fromCompilations(analysis.getCompilations)
      else expected(Classes.Compilations).!!
    Analysis.Empty.copy(
      stamps = stamps,
      relations = relations,
      infos = sourceInfos,
      compilations = compilations
    )
  }

  def fromAnalysisFile(analysisFile: Schema.AnalysisFile): (Analysis, MiniSetup, Schema.Version) = {
    val version = analysisFile.getVersion
    val analysis =
      if (analysisFile.hasAnalysis) fromAnalysis(analysisFile.getAnalysis)
      else s"The analysis file from format ${version} could not be read.".!!
    val miniSetup =
      if (analysisFile.hasMiniSetup) fromMiniSetup(analysisFile.getMiniSetup)
      else s"The mini setup from format ${version} could not be read.".!!
    (analysis, miniSetup, version)
  }
  private class StringTable {
    private val strings = new JHashMap[String, String]()
    def lookupOrEnter(string: String): String = {
      strings.putIfAbsent(string, string) match {
        case null => string
        case v    => v
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy