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

com.nawforce.apexlink.org.ReferenceProvider.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2022 FinancialForce.com, inc. All rights reserved.
 */
package com.nawforce.apexlink.org

import com.nawforce.apexlink.cst.ExprContext
import com.nawforce.apexlink.memory.SkinnySet
import com.nawforce.apexlink.org.ReferenceProvider.emptyTargetLocations
import com.nawforce.apexlink.rpc.TargetLocation
import com.nawforce.apexlink.types.apex.{
  ApexClassDeclaration,
  ApexFullDeclaration,
  FullDeclaration,
  PreReValidatable,
  SummaryDeclaration
}
import com.nawforce.apexlink.types.core.{Dependent, TypeDeclaration, TypeId}
import com.nawforce.pkgforce.path.{PathLike, PathLocation}

import scala.reflect.ClassTag
import scala.util.hashing.MurmurHash3

trait Referenceable extends PreReValidatable {
  this: Dependent =>
  private var referenceLocations: SkinnySet[PathLocation] = new SkinnySet()
  private var cache: (Int, Set[TargetLocation])           = (0, Set.empty)

  override def preReValidate(): Unit = {
    super.preReValidate()
    referenceLocations = new SkinnySet()
  }

  def getTargetLocations: Array[TargetLocation] = {
    referenceLocations.toSet
      .map(ref => TargetLocation(ref.path.toString, ref.location))
      .toArray
  }

  def addLocation(pathLocation: PathLocation): Unit = {
    referenceLocations.add(pathLocation)
  }

  def findReferences(): Set[TargetLocation] = {
    val hashCode = hash()
    if (hashCode != cache._1)
      cache = (hashCode, collectReferences())
    cache._2
  }

  def doesNeedReValidation(): Boolean = {
    hash() != cache._1
  }

  /** This represents a high level view of all the types that hold a reference to this object.
    * Returns the types that needs can be revalidated to build up the more specific reference
    * locations.
    */
  def getReferenceHolderTypeIds: Set[TypeId]

  /** Returns the detailed reference locations
    */
  protected def collectReferences(): Set[TargetLocation]

  private def hash(): Int = {
    MurmurHash3.arrayHash(
      getReferenceHolderTypeIds.toArray
        .flatMap(id => id.toTypeDeclaration[ApexClassDeclaration])
        .map(_.deepHash)
    )
  }
}

trait ReferenceProvider extends SourceOps {
  this: OPM.PackageImpl =>

  def getReferences(path: PathLike, line: Int, offset: Int): Array[TargetLocation] = {
    val sourceTd = loadTypeFromModule(path) match {
      case Some(sm: SummaryDeclaration) =>
        reValidate(Set(sm.typeId) ++ sm.getTypeDependencyHolders.toSet)
        // Reload the source after summary type has been validated so we can get the full type
        loadTypeFromModule(path).getOrElse(return emptyTargetLocations)
      case Some(td) => td
      case None     => return emptyTargetLocations
    }

    val expr = getExpressionFromValidation(sourceTd, line, offset)

    if (expr.nonEmpty && expr.get.locatable.isEmpty)
      return emptyTargetLocations

    expr
      .flatMap(_.locatable)
      .orElse({
        sourceTd match {
          case fd: FullDeclaration =>
            fd.getBodyDeclarationFromLocation(line, offset)
              .map(_._2)
        }
      })
      .flatMap({
        case ref: Referenceable =>
          // ReValidate any references so that ReferenceLocations can be built up
          if (ref.doesNeedReValidation())
            reValidate(ref.getReferenceHolderTypeIds)
          Some(ref.findReferences().toArray)
        case _ => None
      })
      .getOrElse(emptyTargetLocations)
  }

  private def getExpressionFromValidation(
    td: TypeDeclaration,
    line: Int,
    offset: Int
  ): Option[ExprContext] = {
    td match {
      case td: ApexFullDeclaration =>
        val result = locateFromValidation(td, line, offset)
        result._2.map(loc => result._1(loc).result)
      case _ => None
    }

  }

}

object ReferenceProvider {
  private val emptyTargetLocations: Array[TargetLocation] = Array.empty
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy