11package xitrum .routing
22
3+ import java .lang .reflect .Modifier
34import scala .collection .mutable .ArrayBuffer
45import scala .reflect .runtime .universe
56
@@ -10,43 +11,46 @@ import xitrum.annotation.ActionAnnotations
1011 * Intended for use by RouteCollector.
1112 *
1213 * For each .class file, RouteCollector uses ASM's ClassReader to load it, then
13- * calls "addBranches" to pass its class name and interface names. Action is a
14- * trait. Traits are seen by ASM as interfaces. At this step, we build trees of
15- * interface -> children.
14+ * calls "addBranches" to pass its class name, super class name, and interface names.
15+ * At this step, we build trees of parent -> children.
1616 *
1717 * Lastly, RouteCollector calls "getConcreteActionsAndAnnotations" to get
18- * concrete (non-trait) action classes and their annotations.
18+ * concrete (non-trait, non-abstract ) action classes and their annotations.
1919 *
2020 * This class is immutable because it will be serialized to routes.cache.
2121 *
22- * @param interface2Children Map to save trees; names are class names
22+ * @param parent2Children Map to save trees; names are class names
2323 */
24- private case class ActionTreeBuilder (interface2Children : Map [String , Seq [String ]] = Map ()) {
24+ private case class ActionTreeBuilder (parent2Children : Map [String , Seq [String ]] = Map ()) {
2525 def addBranches (
26- childInternalName : String ,
27- parentInternalNames : Array [String ]
26+ childInternalName : String ,
27+ superInternalName : String ,
28+ interfaceInternalNames : Array [String ]
2829 ): ActionTreeBuilder = {
29- // Class name: xitrum.Action
30- // Internal name: xitrum/Action
31- def internalName2ClassName (internalName : String ) = internalName.replace('/' , '.' )
30+ if (superInternalName == null || interfaceInternalNames == null ) return this
3231
33- if (parentInternalNames == null ) return this
32+ // Optimize: Ignore Java and Scala default classes; these can be thousands
33+ if (childInternalName.startsWith(" java/" ) ||
34+ childInternalName.startsWith(" javax/" ) ||
35+ childInternalName.startsWith(" scala/" ) ||
36+ childInternalName.startsWith(" sun/" ) ||
37+ childInternalName.startsWith(" com/sun/" )) return this
3438
35- val childClassName = internalName2ClassName(childInternalName)
39+ val parentInternalNames = Seq (superInternalName) ++ interfaceInternalNames
40+ val parentClassNames = parentInternalNames.map(internalName2ClassName _)
3641
37- var i2c = interface2Children
38- parentInternalNames.foreach { parentInternalName =>
39- val parentClassName = internalName2ClassName(parentInternalName)
40- if (interface2Children.isDefinedAt(parentClassName)) {
41- val children = interface2Children(parentClassName)
42+ val childClassName = internalName2ClassName(childInternalName)
43+ val p2c = parentClassNames.foldLeft(parent2Children) { case (acc, parentClassName) =>
44+ if (acc.isDefinedAt(parentClassName)) {
45+ val children = parent2Children(parentClassName)
4246 val newChildren = children :+ childClassName
43- i2c += parentClassName -> newChildren
47+ acc + ( parentClassName -> newChildren)
4448 } else {
45- i2c += parentClassName -> Seq (childClassName)
49+ acc + ( parentClassName -> Seq (childClassName) )
4650 }
4751 }
4852
49- ActionTreeBuilder (i2c )
53+ ActionTreeBuilder (p2c )
5054 }
5155
5256 /**
@@ -69,19 +73,22 @@ private case class ActionTreeBuilder(interface2Children: Map[String, Seq[String]
6973 case Some (aa) => aa
7074
7175 case None =>
72- val parents = klass.getInterfaces
73- val parentAnnotations = parents.foldLeft(ActionAnnotations ()) { case (acc, parent) =>
74- if (classOf [Action ].isAssignableFrom(parent)) {
75- val aa = getActionAccumulatedAnnotations(parent.asInstanceOf [Class [_ <: Action ]])
76- acc.overrideMe(aa)
76+ val parentClasses = Seq (klass.getSuperclass) ++ klass.getInterfaces
77+ val parentAnnotations = parentClasses.foldLeft(ActionAnnotations ()) { case (acc, parentClass) =>
78+ // parentClass is null if klass is a trait/interface
79+ if (parentClass == null ) {
80+ acc
81+ } else if (classOf [Action ].isAssignableFrom(parentClass)) {
82+ val aa = getActionAccumulatedAnnotations(parentClass.asInstanceOf [Class [_ <: Action ]])
83+ acc.inherit(aa)
7784 } else {
7885 acc
7986 }
8087 }
8188
82- val annotations = runtimeMirror.classSymbol(klass).asClass.annotations
83- val ret = parentAnnotations.overrideMe(annotations )
84-
89+ val universeAnnotations = runtimeMirror.classSymbol(klass).asClass.annotations
90+ val thisAnnotationsOnly = ActionAnnotations .fromUniverse(universeAnnotations )
91+ val ret = thisAnnotationsOnly.inherit(parentAnnotations)
8592 cache += klass -> ret
8693 ret
8794 }
@@ -92,15 +99,20 @@ private case class ActionTreeBuilder(interface2Children: Map[String, Seq[String]
9299
93100 // ----------------------------------------------------------------------------
94101
102+ // Class name: xitrum.Action
103+ // Internal name: xitrum/Action
104+ private def internalName2ClassName (internalName : String ) = internalName.replace('/' , '.' )
105+
95106 private def getConcreteActions : Set [Class [_ <: Action ]] = {
96107 var concreteActions = Set [Class [_ <: Action ]]()
97108
98109 def traverseActionTree (className : String ) {
99- if (interface2Children .isDefinedAt(className)) {
100- val children = interface2Children (className)
110+ if (parent2Children .isDefinedAt(className)) {
111+ val children = parent2Children (className)
101112 children.foreach { className =>
102- val klass = Class .forName(className).asInstanceOf [Class [_ <: Action ]]
103- if (! klass.isInterface) concreteActions += klass // Not interface => concrete
113+ val klass = Class .forName(className)
114+ val concrete = ! klass.isInterface && ! Modifier .isAbstract(klass.getModifiers)
115+ if (concrete) concreteActions += klass.asInstanceOf [Class [_ <: Action ]]
104116 traverseActionTree(className)
105117 }
106118 }
0 commit comments