![JAR search and dependency download from the Maven repository](/logo.png)
org.beangle.commons.lang.reflect.BeanInfoLoader.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of beangle-commons Show documentation
Show all versions of beangle-commons Show documentation
The Beangle Commons Library
The newest version!
/*
* 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.collection.{Collections, IdentityCache}
import org.beangle.commons.lang.Strings.{substringBefore, uncapitalize}
import org.beangle.commons.lang.reflect.BeanInfo
import org.beangle.commons.lang.reflect.BeanInfo.*
import org.beangle.commons.lang.reflect.BeanInfo.Builder.filterSameNames
import org.beangle.commons.lang.reflect.Reflections.deduceParamTypes
import org.beangle.commons.lang.{ClassLoaders, Primitives, Strings}
import java.beans.Transient
import java.lang.Character.isUpperCase
import java.lang.reflect.{Field, Method, Modifier, ParameterizedType, TypeVariable}
import scala.collection.immutable.ArraySeq
import scala.collection.mutable
import scala.reflect.*
/** Load ClassInfo using reflection
*
*/
object BeanInfoLoader {
private case class Getter(method: Method, returnType: TypeInfo)
private case class Setter(method: Method, parameterTypes: Array[TypeInfo])
/** Load BeanInfo using reflections
*/
def load(clazz: Class[_]): BeanInfo = {
val className = clazz.getName
if (className.startsWith("java.") || className.startsWith("scala.") || className.contains("$$"))
throw new RuntimeException("Cannot reflect class:" + clazz.getName)
val isCase = TypeInfo.isCaseClass(clazz)
val getters = new mutable.HashMap[String, Getter]
val setters = new mutable.HashMap[String, Setter]
val fields = new mutable.HashMap[String, Field]
val accessMethods = new mutable.HashSet[MethodInfo]
val accessed = new mutable.HashSet[Class[_]]
var nextClass = clazz
var paramTypes: collection.Map[String, Class[_]] = Map.empty
while (null != nextClass && classOf[AnyRef] != nextClass) {
val declaredMethods = nextClass.getDeclaredMethods
nextClass.getDeclaredFields foreach { f => fields += (f.getName -> f) }
(0 until declaredMethods.length) foreach { i =>
val method = declaredMethods(i)
processMethod(isCase, method, getters, setters, accessMethods, paramTypes)
}
navIterface(nextClass, accessed, getters, setters, accessMethods, paramTypes)
val nextType = nextClass.getGenericSuperclass
nextClass = nextClass.getSuperclass
paramTypes = deduceParamTypes(nextClass, nextType, paramTypes)
}
//find default constructor parameters
val defaultCtorParamValues: Map[Int, Any] =
ClassLoaders.get(clazz.getName + "$") match {
case Some(oclazz) =>
val singleton = oclazz.getDeclaredField("MODULE$").get(null)
val params = Collections.newMap[Int, Any]
oclazz.getDeclaredMethods foreach { m =>
val index = Strings.substringAfter(m.getName, "$lessinit$greater$default$")
if (Strings.isNotEmpty(index)) params.put(Integer.parseInt(index), m.invoke(singleton))
}
params.toMap
case None => Map.empty
}
// find constructor with arguments
val ctors = Collections.newBuffer[BeanInfo.ConstructorInfo]
var pCtorParamNames: Set[String] = Set.empty
var foundDefaultCtor = false
clazz.getConstructors foreach { ctor =>
val params = new mutable.ArrayBuffer[ParamInfo](ctor.getParameterCount)
ctor.getParameters foreach { p => params += ParamInfo(p.getName, typeof(p.getType, p.getParameterizedType, paramTypes), None) }
if (!foundDefaultCtor && defaultCtorParamValues.nonEmpty) {
if (isDefaultParamMatched(defaultCtorParamValues, params)) {
foundDefaultCtor = true
defaultCtorParamValues foreach { case (idx, v) =>
params(idx - 1) = params(idx - 1).copy(defaultValue = Some(v)) // for default values were 1 based.
}
}
}
pCtorParamNames ++= params.map(_.name).toSet
ctors += BeanInfo.ConstructorInfo(ArraySeq.from(params))
}
// make buffer to list and filter duplicated bridge methods
accessMethods.groupBy(_.method.getName).foreach { case (x, sameNames) =>
val nb = Builder.filterSameNames(sameNames)
if (getters.contains(x) && nb.size == 1) getters.put(x, getters(x).copy(method = nb.head.method))
}
// organize setter and getter
val allprops = getters.keySet ++ setters.keySet
val properties = Collections.newMap[String, BeanInfo.PropertyInfo]
allprops foreach { p =>
val getter = getters.get(p)
val setter = setters.get(p)
val typeinfo = if getter.isEmpty then setter.get.parameterTypes(0) else getter.get.returnType
val isTransientAnnoted = fields.get(p).exists(x => Modifier.isTransient(x.getModifiers))
val isTransient = BeanInfo.Builder.isTransient(isTransientAnnoted, setter.isDefined, pCtorParamNames.contains(p))
val pd = BeanInfo.PropertyInfo(p, typeinfo, getter.map(_.method), setter.map(_.method), isTransient)
properties.put(p, pd)
}
// change accessible for none public class
if !Modifier.isPublic(clazz.getModifiers) then
properties foreach { case (k, v) =>
v.getter.foreach { m => m.setAccessible(true) }
v.setter.foreach { m => m.setAccessible(true) }
}
//collect other non-access methods
val methods = new mutable.ArrayBuffer[Method]
clazz.getMethods foreach { method =>
if (Builder.isFineMethod(isCase, method, true)) {
var added = false
Builder.findAccessor(method) foreach { (_, name) =>
added = !properties.contains(name)
}
if !added then methods += method
}
}
val groupMethods = methods.groupBy(_.getName).map(x => (x._1, ArraySeq.from(x._2)))
new BeanInfo(clazz, ArraySeq.from(ctors), properties.toMap, groupMethods)
}
private def isDefaultParamMatched(defaultCtorParamValues: Map[Int, Any], params: collection.Seq[ParamInfo]): Boolean = {
defaultCtorParamValues.forall { case (idx, pv) =>
if ((idx - 1) < params.length) {
pv match
case null => true
case value => Primitives.wrap(params(idx - 1).typeinfo.clazz).isAssignableFrom(Primitives.wrap(value.getClass))
} else {
false
}
}
}
private def navIterface(clazz: Class[_], accessed: mutable.HashSet[Class[_]],
getters: mutable.HashMap[String, Getter], setters: mutable.HashMap[String, Setter],
methods: mutable.HashSet[MethodInfo],
paramTypes: collection.Map[String, Class[_]]): Unit = {
if (null == clazz || classOf[AnyRef] == clazz) return
val isCase = TypeInfo.isCaseClass(clazz)
val interfaceTypes = clazz.getGenericInterfaces
(0 until interfaceTypes.length) foreach { i =>
val interface = interfaceTypes(i) match {
case pt: ParameterizedType => pt.getRawType.asInstanceOf[Class[_]]
case c: Class[_] => c
}
if (!accessed.contains(interface)) {
accessed.add(interface)
val interfaceParamTypes = deduceParamTypes(interface, interfaceTypes(i), paramTypes)
val declaredMethods = interface.getDeclaredMethods
(0 until declaredMethods.length) foreach { i =>
val method = declaredMethods(i)
processMethod(isCase, method, getters, setters, methods, interfaceParamTypes)
}
navIterface(interface, accessed, getters, setters, methods, paramTypes)
}
}
}
private def processMethod(isCase: Boolean, method: Method, getters: mutable.HashMap[String, Getter],
setters: mutable.HashMap[String, Setter],
accessMethods: mutable.HashSet[MethodInfo],
paramTypes: collection.Map[String, Class[_]]): Unit = {
if (BeanInfo.Builder.isFineMethod(isCase, method, false)) {
BeanInfo.Builder.findAccessor(method) match {
case Some(Tuple2(readable, name)) =>
if (readable) {
val puttable = getters.get(name).forall(x => isJavaBeanGetter(x.method)) //FIXME
if puttable then
getters.put(name, Getter(method, typeof(method.getReturnType, method.getGenericReturnType, paramTypes)))
} else {
val types = method.getGenericParameterTypes
val clazzes = method.getParameterTypes
val paramsTypes = new Array[TypeInfo](types.length)
(0 until types.length) foreach { j => paramsTypes(j) = typeof(clazzes(j), types(j), paramTypes) }
setters.put(name, Setter(method, paramsTypes))
}
val types = method.getGenericParameterTypes
val params = new mutable.ArrayBuffer[ParamInfo](types.length)
method.getParameters foreach { p => params += ParamInfo(p.getName, typeof(p.getType, p.getParameterizedType, paramTypes), None) }
accessMethods.add(MethodInfo(method, typeof(method.getReturnType, method.getGenericReturnType, paramTypes), ArraySeq.from(params)))
case None =>
}
}
}
private def isJavaBeanGetter(method: Method): Boolean = {
val name = method.getName
if name.startsWith("get") && name.length > 3 && isUpperCase(name.charAt(3)) then true
else if name.startsWith("is") && name.length > 2 && isUpperCase(name.charAt(2)) then true
else false
}
def typeof(clazz: Class[_], typ: java.lang.reflect.Type, paramTypes: collection.Map[String, Class[_]]): TypeInfo = {
if TypeInfo.isIterableType(clazz) then
if clazz.isArray then
TypeInfo.get(clazz, clazz.getComponentType)
else
typ match {
case pt: ParameterizedType =>
if (pt.getActualTypeArguments.length == 1) TypeInfo.get(clazz, typeAt(pt, 0))
else TypeInfo.get(clazz, typeAt(pt, 0), typeAt(pt, 1))
case tv: TypeVariable[_] => TypeInfo.get(paramTypes.getOrElse(tv.getName, classOf[AnyRef]))
case _: Class[_] => TypeInfo.get(clazz, false)
case _ => TypeInfo.get(clazz, classOf[Any], classOf[Any])
}
else if clazz == classOf[Option[_]] then
val innerType = typ match {
case pt: ParameterizedType => if (pt.getActualTypeArguments.length == 1) typeAt(pt, 0) else classOf[AnyRef]
case c: Class[_] => classOf[AnyRef]
}
TypeInfo.get(innerType, optional = true)
else
TypeInfo.get(clazz)
}
private def typeAt(typ: java.lang.reflect.Type, idx: Int): Class[_] = {
typ match {
case c: Class[_] => c
case pt: ParameterizedType =>
pt.getActualTypeArguments()(idx) match {
case c: Class[_] => c
case _ => classOf[AnyRef]
}
case _ => classOf[AnyRef]
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy