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

za.co.absa.spline.common.ReflectionUtils.scala Maven / Gradle / Ivy

/*
 * Copyright 2017 ABSA Group Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package za.co.absa.spline.common

import scala.collection.concurrent.TrieMap
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox

/**
  * Reflection utils
  */
object ReflectionUtils {

  private val mirror: Mirror = runtimeMirror(getClass.getClassLoader)
  private val gettersCache = TrieMap.empty[ClassSymbol, Iterable[Symbol]]

  object ModuleClassSymbolExtractor {
    def unapply(o: Any): Option[ClassSymbol] = {
      val symbol = mirror.classSymbol(o.getClass)
      if (symbol.isModuleClass) Some(symbol)
      else None
    }
  }

  def compile[A](code: Tree): Map[String, Any] => A = {
    val tb = mirror.mkToolBox()
    val execFn = tb.compile(
      q"""
        (__args: Map[String, Any]) => {
          def args[T](k: String): T = __args(k).asInstanceOf[T]
          $code
        }
      """)()
    execFn.asInstanceOf[Map[String, Any] => A]
  }

  /**
    * Lists all direct sub-classes of the given sealed type T
    *
    * @tparam T sealed type
    * @return sequence of classes
    */
  def directSubClassesOf[T: TypeTag]: Seq[Class[_ <: T]] = {
    val clazz: ClassSymbol = typeOf[T].typeSymbol.asClass
    require(clazz.isSealed, s"$clazz must be sealed")
    clazz.knownDirectSubclasses.toSeq.map((s: Symbol) =>
      mirror.runtimeClass(s.asClass).asInstanceOf[Class[_ <: T]])
  }

  /**
    * Returns a sequence of all known objects that directly inherit from the given sealed type T
    *
    * @tparam T sealed type
    * @return sequence of object instances
    */
  def objectsOf[T <: AnyRef : TypeTag]: Seq[T] = {
    val clazz: ClassSymbol = typeOf[T].typeSymbol.asClass
    require(clazz.isSealed, s"$clazz must be sealed")
    clazz.knownDirectSubclasses.toSeq
      .filter(_.isModuleClass)
      .map((s: Symbol) => objectForName[T](s.fullName))
  }

  /**
    * Returns an object instance with the specified name.
    * Similar to as Class.forName() returns a Class instance by name, this method returns a Scala object instance by name.
    *
    * @param name fully qualified object instance name
    */
  def objectForName[T <: AnyRef](name: String): T = {
    val moduleSymbol = mirror.staticModule(name)
    mirror.reflectModule(moduleSymbol).instance.asInstanceOf[T]
  }

  def extractFieldValue[T](o: AnyRef, fieldName: String): T = {
    val field = o.getClass.getDeclaredField(fieldName)
    field.setAccessible(true)
    field.get(o).asInstanceOf[T]
  }

  def extractProductElementsWithNames(product: Product): Map[String, _] = {
    val pMirror = mirror.reflect(product)
    constructorArgSymbols(pMirror.symbol)
      .map(argSymbol => {
        val name = argSymbol.name.toString
        val value = pMirror.reflectMethod(argSymbol.asMethod).apply()
        name -> value
      })
      .toMap
  }

  private def constructorArgSymbols(classSymbol: ClassSymbol) =
    gettersCache.getOrElseUpdate(classSymbol, {
      val primaryConstr = classSymbol.primaryConstructor

      val paramNames = (
        for {
          pList <- primaryConstr.typeSignature.paramLists
          pSymbol <- pList
        } yield
          pSymbol.name.toString
        )
        .toSet

      classSymbol.info.decls.filter(d =>
        d.isMethod
          && d.asMethod.isGetter
          && paramNames(d.name.toString))
    })

  def caseClassCtorArgDefaultValue[A](t: Class[_], name: String): Option[A] = {
    val classSymbol = mirror.classSymbol(t)
    val companionMirror = {
      val companionModule = classSymbol.companion.asModule
      mirror.reflect(mirror.reflectModule(companionModule).instance)
    }
    val signature = companionMirror.symbol.typeSignature
    val constructor = classSymbol.primaryConstructor.asMethod

    constructor
      .paramLists
      .flatten
      .view
      .zipWithIndex
      .collect({
        case (arg, i) if arg.name.toString == name =>
          signature.member(TermName(s"apply$$default$$${i + 1}"))
      })
      .collectFirst({
        case member if member != NoSymbol =>
          companionMirror.reflectMethod(member.asMethod)().asInstanceOf[A]
      })
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy