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

org.beangle.commons.lang.reflect.BeanInfoDigger.scala Maven / Gradle / Ivy

/*
 * Copyright (C) 2005, The Beangle Software.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */

package org.beangle.commons.lang.reflect

import org.beangle.commons.lang.Strings
import org.beangle.commons.lang.annotation.noreflect
import org.beangle.commons.lang.reflect.BeanInfo.*
import org.beangle.commons.lang.reflect.BeanInfo.Builder.{ParamHolder, getPropertyName}

import java.lang.reflect.Method
import scala.collection.immutable.ArraySeq
import scala.collection.mutable
import scala.quoted.*

object BeanInfoDigger {
  def digInto(argsExpr: Expr[Seq[Class[_]]], cache: Expr[BeanInfoCache])(using Quotes): Expr[List[BeanInfo]] = {
    import quotes.reflect.*
    argsExpr match {
      case Varargs(cls) =>
        val biList = cls.map { cl =>
          cl.asTerm match {
            case TypeApply(term, trees) => new BeanInfoDigger[quotes.type](trees.head.tpe).dig()
          }
        }
        '{
          val bis = ${ Expr.ofList(biList) }
          bis.foreach { bi => ${ cache }.update(bi) }
          bis
        }
      case _ =>
        report.error(s"Args must be explicit", argsExpr)
        '{ ??? }
    }
  }

  def digInto[T: Type](ec: Expr[Class[T]], cache: Expr[BeanInfoCache])(implicit quotes: Quotes): Expr[BeanInfo] = {
    import quotes.reflect.*
    val digger = new BeanInfoDigger[quotes.type](quotes.reflect.TypeRepr.of[T])
    '{
      ${ cache }.update(${ digger.dig() })
    }
  }
}

class BeanInfoDigger[Q <: Quotes](trr: Any)(using val q: Q) {

  import q.reflect.*

  val typeRepr = trr.asInstanceOf[TypeRepr]

  def dig(): Expr[BeanInfo] = {
    '{
      val b = new BeanInfo.Builder(${ typeOf(typeRepr) })
      ${ Expr.block(addMemberBody('b), 'b) }.build()
    }
  }

  def typeOf(tpe: TypeRepr): Expr[Class[?]] =
    Literal(ClassOfConstant(tpe)).asExpr.asInstanceOf[Expr[Class[?]]]

  case class FieldExpr(name: String, typeinfo: Expr[AnyRef], transntAnnotated: Boolean, hasGet: Boolean, hasSet: Boolean, defaultValue: Option[Expr[Any]] = None) {
    def transnt(constructParamNames: Set[String]): Boolean = BeanInfo.Builder.isTransient(transntAnnotated, hasSet, constructParamNames.contains(name))
  }

  case class ParamExpr(name: String, typeinfo: Expr[AnyRef], defaultValue: Option[Expr[Any]] = None)

  case class MethodExpr(name: String, rt: Expr[Any], params: Seq[ParamExpr], asField: Boolean)

  def isNormal(name: String): Boolean = {
    !name.contains("$") && !name.startsWith("_")
  }

  def findAccessor(m: DefDef): Option[(Boolean, String)] = {
    val name = m.name
    var paramSize = 0
    m.paramss foreach {
      case TermParamClause(ps: List[ValDef]) => paramSize += ps.size
      case _ =>
    }
    if isNormal(name) then
      if (m.paramss.isEmpty && m.returnTpt.tpe.typeSymbol != Symbol.classSymbol(classOf[Unit].getName)) {
        Some((true, getPropertyName(name, true)))
      } else if (1 == paramSize) {
        val propertyName = getPropertyName(name, false)
        if (null != propertyName) Some((false, propertyName)) else None
      } else None
    else None
  }

  private def addMemberBody(t: Expr[BeanInfo.Builder]): List[Expr[_]] = {
    val fieldMap = new mutable.HashMap[String, FieldExpr]
    val typeSymbol = typeRepr.typeSymbol
    val isScalaClass = !typeSymbol.flags.is(Flags.JavaDefined)
    val isCaseClass = typeRepr.typeSymbol.caseFields.nonEmpty
    val ctorDeclarations = typeSymbol.declarations.filter(_.isClassConstructor).toBuffer
    // dotty will add this(x01:Unit) method in class as primary constructor,we ignore it.
    ctorDeclarations -= typeSymbol.primaryConstructor
    if isScalaClass then ctorDeclarations.prepend(typeSymbol.primaryConstructor)

    val ctorDefaults = resolveCtorDefaults(typeSymbol)
    var i = 0
    val ctors = ctorDeclarations.map { s =>
      val defdef = s.tree.asInstanceOf[DefDef]
      i += 1
      resolveDefParams(defdef, Map.empty, if i == 1 then ctorDefaults else Map.empty)
    }

    val superBases = Set("scala.Any", "scala.Matchable", "java.lang.Object", "scala.Equals", "scala.Product", "java.io.Serializable")
    for (bc <- typeRepr.baseClasses if !superBases.contains(bc.fullName)) {
      val base = typeRepr.baseType(bc)
      var params = Map.empty[String, TypeRepr]
      base match {
        case a: AppliedType => params = resolveClassTypes(a)
        case _ =>
      }

      //Some fields declared in primary constructor will by ignored due to missing public access methods.
      //So we discover declared fields,they may appear in that collection.
      base.typeSymbol.declaredFields foreach { mm =>
        val tpe = mm.tree.asInstanceOf[ValDef].tpt.tpe
        val transnt = mm.annotations exists (x => x.show.toLowerCase.contains("transient"))
        val noreflect = mm.hasAnnotation(Symbol.classSymbol(classOf[noreflect].getName))
        val isPublic = !mm.flags.is(Flags.Protected) && !mm.flags.is(Flags.Private)
        val isInnerType = mm.name == Strings.substringBetween(mm.tree.show, "this.", ".type")
        if isPublic && isNormal(mm.name) && !noreflect && !isInnerType then fieldMap.put(mm.name, FieldExpr(mm.name, resolveType(tpe, params), transnt, true, true))
      }

      base.typeSymbol.declaredMethods foreach { mm =>
        val defdef = mm.tree.asInstanceOf[DefDef]
        val rtType = resolveType(defdef.returnTpt.tpe, params)
        val isPublic = !defdef.symbol.flags.is(Flags.Protected) && !defdef.symbol.flags.is(Flags.Private)
        val ignored = isCaseClass && Set("productPrefix", "productArity", "productIterator", "productElementNames").contains(defdef.name)
        if (isPublic && isNormal(defdef.name) && !ignored) {
          this.findAccessor(defdef) foreach { (readable, name) =>
            fieldMap.get(name) match {
              case Some(fx) => if readable then fieldMap.put(name, fx.copy(hasGet = true)) else fieldMap.put(name, fx.copy(hasSet = true))
              case None =>
                val paramList = resolveDefParams(defdef, params, Map.empty)
                val transnt = defdef.symbol.annotations exists (x => x.show.toLowerCase.contains("transient"))
                val fe = if readable then FieldExpr(name, rtType, transnt, true, false) else FieldExpr(name, paramList.head.typeinfo, transnt, false, true)
                fieldMap.put(name, fe)
            }
          }
        }
      }
    }
    val members = new mutable.ArrayBuffer[Expr[_]]()
    if !(ctors.size == 1 && ctors.head.isEmpty) then
      members ++= ctors.map { m =>
        val paramInfos = m.map { p =>
          if (p.defaultValue.isEmpty) '{ new ParamHolder(${ Expr(p.name) }, ${ p.typeinfo }) }
          else '{ new ParamHolder(${ Expr(p.name) }, ${ p.typeinfo }, Some(${ p.defaultValue.get })) }
        }
        '{ ${ t }.addCtor(Array(${ Varargs(paramInfos) }: _*)) }
      }
    end if

    val primaryCtorParamNames = ctors.headOption match {
      case Some(ctor) => ctor.map(_.name).toSet
      case None => Set.empty
    }

    val transients = fieldMap.values.filter(x => x.transnt(primaryCtorParamNames)).map(x => Expr(x.name)).toList
    if (transients.nonEmpty) {
      members += '{ ${ t }.addTransients(Array(${ Varargs(transients) }: _*)) }
    }
    members ++= fieldMap.values.map { x =>
      '{ ${ t }.addField(${ Expr(x.name) }, ${ x.typeinfo }) }
    }
    members.toList
  }

  def resolveType(typeRepr: TypeRepr, params: Map[String, TypeRepr]): Expr[AnyRef] = {
    var tpe = typeRepr
    var args: List[Expr[AnyRef]] = List.empty
    tpe match {
      case d: TypeRef => if (tpe.typeSymbol.flags.is(Flags.Param) && params.contains(tpe.typeSymbol.name)) tpe = params(tpe.typeSymbol.name)
      case c: AppliedType => args = resolveParamTypes(c, params)
      case d: AnnotatedType => tpe = d.underlying
      case c: ConstantType =>
      case _ => throw new RuntimeException("Unspported type :" + tpe)
    }
    if args.isEmpty then typeOf(tpe)
    else '{ Array(${ typeOf(tpe) }, Array(${ Varargs(args) }: _*)) }
  }

  def resolveClassTypes(a: AppliedType, ctx: Map[String, TypeRepr] = Map.empty): Map[String, TypeRepr] = {
    val params = new mutable.HashMap[String, TypeRepr]
    val mts = a.typeSymbol.typeMembers
    var i = 0
    a.args foreach { arg =>
      val argType = if (arg.typeSymbol.flags.is(Flags.Param)) then ctx(arg.typeSymbol.name) else arg
      params.put(mts(i).name, argType)
      i += 1
    }
    params.toMap
  }

  def resolveParamTypes(a: AppliedType, ctx: Map[String, TypeRepr] = Map.empty): List[Expr[AnyRef]] = {
    var i = 0
    val params = new mutable.ArrayBuffer[Expr[AnyRef]]
    a.args foreach { arg =>
      arg match {
        case d: TypeRef =>
          val argType = if arg.typeSymbol.flags.is(Flags.Param) && ctx.contains(arg.typeSymbol.name) then ctx(arg.typeSymbol.name) else d
          params += typeOf(argType)
        case c: AppliedType =>
          params += '{ Array(${ typeOf(c) }, Array(${ Varargs(resolveParamTypes(c, ctx)) }: _*)) }
        case tb: TypeBounds => typeOf(tb)
      }
      i += 1
    }
    params.toList
  }

  def resolveCtorDefaults(symbol: Symbol): Map[Int, Expr[Any]] = {
    val comp = symbol.companionClass
    if (comp != Symbol.noSymbol) {
      val body = comp.tree.asInstanceOf[ClassDef].body
      val idents: List[(Int, Expr[Any])] =
        for case deff@DefDef(name, _, _, _) <- body
            if name.startsWith("$lessinit$greater$default$")
        yield (name.substring("$lessinit$greater$default$".length).toInt, Ref(deff.symbol).asExpr)
      idents.toMap
    } else {
      Map.empty
    }
  }

  def resolveDefParams(defdef: DefDef, typeParams: Map[String, TypeRepr], defaults: Map[Int, Expr[Any]]): List[ParamExpr] = {
    val paramList = new mutable.ArrayBuffer[ParamExpr]
    defdef.paramss foreach { a =>
      a match {
        case TermParamClause(ps: List[ValDef]) =>
          var i = 0
          paramList ++= ps.map { vl =>
            i += 1
            ParamExpr(vl.name, resolveType(vl.tpt.tpe, typeParams), defaults.get(i))
          }
        case _ =>
      }
    }
    paramList.toList
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy