11package code .util
22
3- import java .io .File
4-
5- import com .openbankproject .commons .model .Bank
63import code .util .Helper .MdcLoggable
74import org .apache .commons .lang3 .StringUtils
8- import org .clapper .classutil .{ClassFinder , ClassInfo }
5+ import org .reflections .Reflections
6+ import org .reflections .scanners .Scanners
7+ import org .reflections .util .{ClasspathHelper , ConfigurationBuilder }
98import com .openbankproject .commons .util .ReflectUtils
109
10+ import scala .jdk .CollectionConverters ._
1111import scala .reflect .runtime .universe .TypeTag
1212
1313/**
14- * this is some util method to scan any class according some rules
14+ * Utility methods to scan classes using Reflections library.
15+ * Replaces classutil (org.clapper) which does not support Fat JAR environments.
1516 * @author shuang
1617 */
1718object ClassScanUtils extends MdcLoggable {
1819
19- lazy val finder = ClassFinder (getClassPath(this .getClass, classOf [Bank ], classOf [String ]))
20+ // Scan the "code" package only to avoid scanning all dependencies
21+ lazy val reflections : Reflections = {
22+ val config = new ConfigurationBuilder ()
23+ .setUrls(ClasspathHelper .forPackage(" code" ))
24+ .setScanners(Scanners .SubTypes .filterResultsBy(_ => true ))
25+ new Reflections (config)
26+ }
2027
2128 /**
2229 * get companion object or singleton object by class name
2330 * @param name object class name
2431 * @tparam U expect type
2532 * @return companion object or singleton object
2633 */
27- def companion [U : TypeTag ](name : String ) : U = {
28- val className = if (name.endsWith(" $" )) name else name + " $"
34+ def companion [U : TypeTag ](name : String ): U = {
35+ val className = if (name.endsWith(" $" )) name else name + " $"
2936 Class .forName(className).getDeclaredField(" MODULE$" ).get(null ).asInstanceOf [U ]
3037 }
3138
3239 /**
3340 * scan classpath to get all companion objects or singleton objects those implements given trait
3441 * @tparam T the trait type parameter
35- * @return all companion objects or singleton object those implements given clazz
42+ * @return all companion objects or singleton objects those implement the given trait
3643 */
37- def getSubTypeObjects [T : TypeTag ]: List [T ] = {
44+ def getSubTypeObjects [T : TypeTag ]: List [T ] = {
3845 val clazz = ReflectUtils .typeTagToClass[T ]
39- val classes = try {
40- val allClasses = finder.getClasses().toList
41- logger.info(s " ClassScanUtils successfully scanned ${allClasses.size} classes from classpath " )
42- allClasses
46+ try {
47+ val subTypes = reflections.getSubTypesOf(clazz).asScala.toList
48+ logger.info(s " ClassScanUtils (Reflections) found ${subTypes.size} subtypes of ${clazz.getName}" )
49+ // companion objects have a class name ending with "$"
50+ val objects = subTypes
51+ .filter(c => c.getName.endsWith(" $" ))
52+ .flatMap { c =>
53+ try { Some (companion[T ](c.getName)) }
54+ catch { case e : Exception =>
55+ logger.warn(s " Failed to load companion object ${c.getName}: ${e.getMessage}" )
56+ None
57+ }
58+ }
59+ logger.info(s " Found ${objects.size} companion objects implementing ${clazz.getName}" )
60+ objects
4361 } catch {
44- case e : UnsupportedOperationException =>
45- // ASM version is too old for some class files (e.g. requires ASM7). In that case,
46- // skip scanned APIs instead of failing the whole application.
47- logger.warn(s " Class scanning failed with UnsupportedOperationException: ${e.getMessage}" )
48- logger.warn(" This is expected when running from a Fat JAR. Scanned APIs will not be auto-registered." )
49- Seq .empty
5062 case e : Exception =>
51- logger.warn(s " Class scanning failed with ${e.getClass.getSimpleName }: ${e.getMessage}" )
52- Seq .empty
63+ logger.warn(s " ClassScanUtils (Reflections) failed for ${clazz.getName }: ${e.getMessage}" )
64+ Nil
5365 }
54- val filtered = classes.filter(_.implements(clazz.getName))
55- logger.info(s " Found ${filtered.size} classes implementing ${clazz.getName}" )
56- filtered.map(_.name).map(companion[T ](_)).toList
5766 }
5867
5968 /**
60- * find all fit classes, do filter with predict function
61- * @param predict check whether include this type in the result
62- * @return all fit type names
69+ * find all fit classes, filtered by a predicate on the Class object.
70+ * @param predict check whether to include this class in the result
71+ * @return all matching class names (without trailing "$")
6372 */
64- def findTypes (predict : ClassInfo => Boolean ): List [String ] = {
65- val classes = try {
66- finder.getClasses().toList
73+ def findTypes (predict : Class [_] => Boolean ): List [String ] = {
74+ try {
75+ // getSubTypesOf(Object) returns all known classes in the scanned packages
76+ reflections.getSubTypesOf(classOf [Object ]).asScala.toList
77+ .filter { c =>
78+ try { predict(c) }
79+ catch { case _ : Exception => false }
80+ }
81+ .map { c =>
82+ val name = c.getName
83+ if (name.endsWith(" $" )) name.substring(0 , name.length - 1 ) else name
84+ }
6785 } catch {
68- case _ : UnsupportedOperationException =>
69- Seq .empty
86+ case e : Exception =>
87+ logger.warn(s " ClassScanUtils.findTypes failed: ${e.getMessage}" )
88+ Nil
7089 }
71- classes
72- .filter(predict)
73- .map(it => {
74- val name = it.name
75- if (name.endsWith(" $" )) name.substring(0 , name.length - 1 )
76- else name
77- }) // some companion type name ends with $, it added by scalac, should remove from class name
78- .toList
7990 }
8091
8192 /**
82- * get given class exists jar Files
83- * @param classes to find class paths contains these class files
84- * @return this class exists jar File
85- */
86- private [this ] def getClassPath (classes : Class [_]* ): Seq [File ] = classes.map { clazz =>
87- val classFile = " /" + clazz.getName.replace('.' , '/' ) + " .class"
88- val uri = clazz.getResource(classFile).toURI.toString
89- val path = uri.replaceFirst(" ^(jar:|file:){0,2}(.*?)\\ !?\\ Q" + classFile + " \\ E$" , " $2" )
90- new File (path)
91- }
92-
93- /**
94- * get all subtype of net.liftweb.mapper.LongKeyedMapper, so we can register scanned db models dynamic
93+ * get all subtype of net.liftweb.mapper.LongKeyedMapper, so we can register scanned db models dynamically
9594 * @param packageName scanned root package name
96- * @return all scanned ClassInfo
95+ * @return all matching class names
9796 */
98- def getMappers (packageName: String = " " ): Seq [ClassInfo ] = {
99- val mapperInterface = " net.liftweb.mapper.LongKeyedMapper"
100- val classes = try {
101- finder.getClasses().toList
97+ def getMappers (packageName : String = " " ): Seq [String ] = {
98+ try {
99+ val mapperInterface = Class .forName(" net.liftweb.mapper.LongKeyedMapper" )
100+ val all = reflections.getSubTypesOf(mapperInterface).asScala.toSeq
101+ .map(_.getName)
102+ if (StringUtils .isNotBlank(packageName))
103+ all.filter(_.startsWith(packageName))
104+ else
105+ all
102106 } catch {
103- case _ : UnsupportedOperationException =>
104- Seq .empty
105- }
106- val infos = classes.filter(it => it.interfaces.contains(mapperInterface))
107- if (StringUtils .isNoneBlank()) {
108- infos.filter(classInfo => classInfo.name.startsWith(packageName))
109- } else {
110- infos
107+ case e : Exception =>
108+ logger.warn(s " ClassScanUtils.getMappers failed: ${e.getMessage}" )
109+ Nil
111110 }
112111 }
113112
114- }
113+ }
0 commit comments