This is an automated email from the ASF dual-hosted git repository.

sergeykamov pushed a commit to branch NLPCRAFT-473
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git


The following commit(s) were added to refs/heads/NLPCRAFT-473 by this push:
     new e7f8d3e  WIP.
e7f8d3e is described below

commit e7f8d3e0de649302233aa4eb133a5821d584c8c2
Author: Sergey Kamov <[email protected]>
AuthorDate: Sat Jan 29 12:14:37 2022 +0300

    WIP.
---
 .../nlpcraft/internal/impl/NCModelScanner.scala    | 414 ++++++++++-----------
 .../impl/scan/NCModelIntentsInvalidArgsSpec.scala  |  22 +-
 .../impl/scan/NCModelIntentsNestedSpec.scala       |   6 +-
 .../internal/impl/scan/NCModelIntentsSpec.scala    |   2 +-
 4 files changed, 222 insertions(+), 222 deletions(-)

diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
index dbc0839..973317b 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
@@ -75,11 +75,23 @@ object NCModelScanner extends LazyLogging:
         CLS_JAVA_OPT
     )
 
-    private case class CallbackHolder(method: Method, function: NCIntentMatch 
=> NCResult)
+    /**
+      *
+      * @param intent
+      * @param function
+      * @param method
+      */
     private case class IntentHolder(intent: NCIDLIntent, function: 
NCIntentMatch => NCResult, method: Method)
 
     /**
       *
+      */
+    private object IntentHolder:
+        def apply(cfg: NCModelConfig, intent: NCIDLIntent, obj: Object, mtd: 
Method): IntentHolder =
+            new IntentHolder(intent, prepareCallback(cfg, mtd, obj, intent), 
mtd)
+
+    /**
+      *
       * @param cls
       * @return
       */
@@ -127,14 +139,15 @@ object NCModelScanner extends LazyLogging:
 
     /**
       *
-      * @param mdlId
+      * @param cfg
       * @param mtd
       * @param paramClss
       * @param argsList
       * @param ctxFirstParam
       * @return
       */
-    private def prepareParams(mdlId: String, mtd: Method, paramClss: 
Seq[Class[_]], argsList: Seq[util.List[NCEntity]], ctxFirstParam: Boolean): 
Seq[AnyRef] =
+    private def prepareParams(cfg: NCModelConfig, mtd: Method, paramClss: 
Seq[Class[_]], argsList: Seq[util.List[NCEntity]], ctxFirstParam: Boolean): 
Seq[AnyRef] =
+        val mdlId = cfg.getId
         paramClss.zip(argsList).zipWithIndex.map { case ((paramCls, argList), 
i) =>
             def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
 
@@ -174,13 +187,14 @@ object NCModelScanner extends LazyLogging:
 
     /**
       *
-      * @param mdlId
+      * @param cfg
       * @param method
       * @param obj
       * @param args
       * @return
       */
-    private def invoke(mdlId: String, method: Method, obj: Object, args: 
scala.Array[AnyRef]): NCResult =
+    private def invoke(cfg: NCModelConfig, method: Method, obj: Object, args: 
scala.Array[AnyRef]): NCResult =
+        val mdlId = cfg.getId
         val methodObj = if Modifier.isStatic(method.getModifiers) then null 
else obj
         var flag = method.canAccess(methodObj)
 
@@ -211,12 +225,13 @@ object NCModelScanner extends LazyLogging:
 
     /**
       *
-      * @param mdlId
+      * @param cfg
       * @param field
       * @param obj
       * @return
       */
-    private def getFieldObject(mdlId: String, field: Field, obj: Object): 
Object =
+    private def getFieldObject(cfg: NCModelConfig, field: Field, obj: Object): 
Object =
+        val mdlId = cfg.getId
         lazy val fStr = field2Str(field)
         val fieldObj = if Modifier.isStatic(field.getModifiers) then null else 
obj
         var flag = field.canAccess(fieldObj)
@@ -275,32 +290,72 @@ object NCModelScanner extends LazyLogging:
         val claxx = o.getClass
         (claxx.getDeclaredFields ++ claxx.getFields).toSet
 
-import org.apache.nlpcraft.internal.impl.NCModelScanner.*
+    /**
+      *
+      * @param cfg
+      * @param mtd
+      * @return
+      */
+    private def scanSamples(cfg: NCModelConfig, mtd: Method): Map[String, 
Seq[Seq[String]]] =
+        val smpAnns = mtd.getAnnotationsByType(CLS_SAMPLE)
+        val smpAnnsRef = mtd.getAnnotationsByType(CLS_SAMPLE_REF)
+        lazy val mtdStr = method2Str(mtd)
+        lazy val intAnns = mtd.getAnnotationsByType(CLS_INTENT)
+        lazy val refAnns = mtd.getAnnotationsByType(CLS_INTENT_REF)
+        lazy val samples = mutable.HashMap.empty[String, Seq[Seq[String]]]
+
+        if smpAnns.nonEmpty || smpAnnsRef.nonEmpty then
+            if intAnns.isEmpty && refAnns.isEmpty then
+                E(s"@NCIntentSample or @NCIntentSampleRef annotations without 
corresponding @NCIntent or @NCIntentRef annotations: $mtdStr")
+            else
+                def read[T](annArr: scala.Array[T], annName: String, 
getSamples: T => Seq[String], getSource: Option[T => String]): Seq[Seq[String]] 
=
+                    for (ann <- annArr.toSeq) yield
+                        val samples = getSamples(ann).map(_.strip).filter(s => 
s.nonEmpty && s.head != '#')
 
-/**
-  *
-  * @param mdl
-  */
-class NCModelScanner(mdl: NCModel) extends LazyLogging:
-    require(mdl != null)
+                        if samples.isEmpty then
+                            getSource match
+                                case None => logger.warn(s"$annName annotation 
has no samples: $mtdStr")
+                                case Some(f) => logger.warn(s"$annName 
annotation references '${f(ann)}' file that has no samples: $mtdStr")
+
+                            Seq.empty
+                        else
+                            samples
+                                .filter(_.nonEmpty)
+
+                val seqSeq =
+                    read[NCIntentSample](
+                        smpAnns, "@NCIntentSample", _.value.toSeq, None
+                    ) ++
+                        read[NCIntentSampleRef](
+                            smpAnnsRef, "@NCIntentSampleRef", a => 
NCUtils.readResource(a.value), Option(_.value)
+                        )
 
-    private final val cfg = mdl.getConfig
-    private final val mdlId = cfg.getId
-    private final val origin = cfg.getOrigin
+                if NCUtils.containsDups(seqSeq.flatMap(_.toSeq).toList) then
+                    logger.warn(s"@NCIntentSample and @NCIntentSampleRef 
annotations have duplicates: $mtdStr")
 
-    private final val intents = mutable.Buffer.empty[IntentHolder]
-    private final val intentDecls = mutable.Buffer.empty[NCIDLIntent]
-    private final val objs = mutable.Buffer.empty[Object]
-    private final val samples = mutable.HashMap.empty[Method, Map[String, 
Seq[Seq[String]]]]
+                val distinct = seqSeq.map(_.distinct).distinct
+
+                for (ann <- intAnns; intent <- 
NCIDLCompiler.compile(ann.value, cfg, mtdStr))
+                    samples += intent.id -> distinct
+
+                for (ann <- refAnns) samples += ann.value -> distinct
+        else if intAnns.nonEmpty || refAnns.nonEmpty then
+            logger.warn(s"@NCIntentSample or @NCIntentSampleRef annotations 
are missing for: $mtdStr")
+
+        samples.toMap
 
     /**
       *
+      * @param cfg
       * @param mtd
       * @param argClasses
       * @param paramGenTypes
       * @param ctxFirstParam
       */
-    private def checkTypes(mtd: Method, argClasses: Seq[Class[_]], 
paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit =
+    private def checkTypes(cfg: NCModelConfig, mtd: Method, argClasses: 
Seq[Class[_]], paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit =
+        val mdlId = cfg.getId
+        val origin = cfg.getOrigin
+
         require(argClasses.sizeIs == paramGenTypes.length)
 
         var warned = false
@@ -315,64 +370,65 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
 
                 if compType != CLS_ENTITY then
                     E(s"Unexpected array element type for @NCIntentTerm 
annotated argument [mdlId=$mdlId, origin=$origin, type=${class2Str(compType)}, 
arg=${mkArg()}]")
-            // Entities collection and optionals.
-            else if COMP_CLS.contains(argClass) then
-                paramGenType match
-                    case pt: ParameterizedType =>
-                        val actTypes = pt.getActualTypeArguments
-                        val compTypes = if actTypes == null then Seq.empty 
else actTypes.toSeq
-
-                        if compTypes.sizeIs != 1 then
-                            E(s"Unexpected generic types count for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
count=${compTypes.length}, arg=${mkArg()}]")
-
-                        val compType = compTypes.head
-
-                        compType match
-                            // Java, Scala, Groovy.
-                            case _: Class[_] =>
-                                val genClass = 
compTypes.head.asInstanceOf[Class[_]]
-                                if genClass != CLS_ENTITY then
-                                    E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${class2Str(genClass)}, arg=${mkArg()}]")
-
-                            // Kotlin.
-                            case _: WildcardType =>
-                                val wildcardType = 
compTypes.head.asInstanceOf[WildcardType]
-                                val lowBounds = wildcardType.getLowerBounds
-                                val upBounds = wildcardType.getUpperBounds
-                                if lowBounds.nonEmpty || upBounds.size != 1 || 
upBounds(0) != CLS_ENTITY then
-                                    E(s"Unexpected Kotlin generic type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${wc2Str(wildcardType)}, arg=${mkArg()}]")
-
-                            case _ => E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${compType.getTypeName}, arg=${mkArg()}]")
-
-                    case _ =>
-                        // Scala.
-                        if COMP_CLS.exists(_ == paramGenType) then
-                            if !warned then
-                                warned = true // TODO: text
-                                logger.warn(s"Method arguments types cannot be 
detected and checked: ${method2Str(mtd)}")
-                            end if
-                        else
-                            E(s"Unexpected parameter type for @NCIntentTerm 
annotated argument [mdlId=$mdlId, origin=$origin, 
type=${paramGenType.getTypeName}, arg=${mkArg()}]")
-            // Other types.
-            else
-                E(s"Unexpected parameter type for @NCIntentTerm annotated 
argument [mdlId=$mdlId, origin=$origin, type=${class2Str(argClass)}, 
arg=${mkArg()}]")
+                // Entities collection and optionals.
+                else if COMP_CLS.contains(argClass) then
+                    paramGenType match
+                        case pt: ParameterizedType =>
+                            val actTypes = pt.getActualTypeArguments
+                            val compTypes = if actTypes == null then Seq.empty 
else actTypes.toSeq
+
+                            if compTypes.sizeIs != 1 then
+                                E(s"Unexpected generic types count for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
count=${compTypes.length}, arg=${mkArg()}]")
+
+                            val compType = compTypes.head
+
+                            compType match
+                                // Java, Scala, Groovy.
+                                case _: Class[_] =>
+                                    val genClass = 
compTypes.head.asInstanceOf[Class[_]]
+                                    if genClass != CLS_ENTITY then
+                                        E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${class2Str(genClass)}, arg=${mkArg()}]")
+
+                                // Kotlin.
+                                case _: WildcardType =>
+                                    val wildcardType = 
compTypes.head.asInstanceOf[WildcardType]
+                                    val lowBounds = wildcardType.getLowerBounds
+                                    val upBounds = wildcardType.getUpperBounds
+                                    if lowBounds.nonEmpty || upBounds.size != 
1 || upBounds(0) != CLS_ENTITY then
+                                        E(s"Unexpected Kotlin generic type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${wc2Str(wildcardType)}, arg=${mkArg()}]")
+
+                                case _ => E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${compType.getTypeName}, arg=${mkArg()}]")
+
+                        case _ =>
+                            // Scala.
+                            if COMP_CLS.exists(_ == paramGenType) then
+                                if !warned then
+                                    warned = true // TODO: text
+                                    logger.warn(s"Method arguments types 
cannot be detected and checked: ${method2Str(mtd)}")
+                                end if
+                            else
+                                E(s"Unexpected parameter type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${paramGenType.getTypeName}, arg=${mkArg()}]")
+                // Other types.
+                else
+                    E(s"Unexpected parameter type for @NCIntentTerm annotated 
argument [mdlId=$mdlId, origin=$origin, type=${class2Str(argClass)}, 
arg=${mkArg()}]")
         }
 
     /**
       *
+      * @param cfg
       * @param mtd
       * @param paramCls
       * @param limits
       * @param ctxFirstParam
       */
-    private def checkMinMax(mtd: Method, paramCls: Seq[Class[_]], limits: 
Seq[(Int, Int)], ctxFirstParam: Boolean): Unit =
+    private def checkMinMax(cfg: NCModelConfig, mtd: Method, paramCls: 
Seq[Class[_]], limits: Seq[(Int, Int)], ctxFirstParam: Boolean): Unit =
         require(paramCls.sizeIs == limits.length)
 
         paramCls.zip(limits).zipWithIndex.foreach { case ((cls, (min, max)), 
i) =>
             def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
 
             val p1 = "its @NCIntentTerm annotated argument"
-            val p2 = s"[mdlId=$mdlId, origin=$origin, arg=${mkArg()}]"
+            val p2 = s"[mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
arg=${mkArg()}]"
 
             // Argument is single entity but defined as not single entity.
             if cls == CLS_ENTITY && (min != 1 || max != 1) then
@@ -390,70 +446,15 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
 
     /**
       *
-      * @param intents
-      * @param intent
-      * @param cb
-      * @param mtd
-      */
-    private def checkBind(intents: mutable.Buffer[IntentHolder], intent: 
NCIDLIntent, cb: CallbackHolder, mtd: Method): Unit =
-        if intents.exists(i => i._1.id == intent.id && i.method != cb.method) 
then
-            E(s"The intent cannot be bound to more than one callback 
[mdlId=$mdlId, origin=$origin, class=${getClassName(mtd.getDeclaringClass)}, 
intentId=${intent.id}]")
-
-    private def bindIntent(intent: NCIDLIntent, cb: CallbackHolder, mtd: 
Method): Unit =
-        if intents.exists(i => i._1.id == intent.id && i.method != cb.method) 
then
-            E(s"The intent cannot be bound to more than one callback 
[mdlId=$mdlId, origin=$origin, class=${getClassName(mtd.getDeclaringClass)}, 
intentId=${intent.id}]")
-        else
-            intentDecls += intent
-            intents += IntentHolder(intent, cb.function, cb.method)
-
-
-    /**
-      *
-      * @param mtd
-      * @param obj
-      */
-    private def processMethod(mtd: Method, obj: Object): Unit =
-        samples += mtd -> scanSamples(mtd)
-
-        def existsForOtherMethod(id: String): Boolean =
-            intents.find(_.intent.id == id) match
-                case Some(i) => i.method != mtd
-                case None => false
-
-        val anns = mtd.getAnnotationsByType(CLS_INTENT)
-
-        if anns.nonEmpty then
-            val mtdStr = method2Str(mtd)
-
-            for (ann <- anns; intent <- NCIDLCompiler.compile(ann.value, cfg, 
mtdStr))
-                if intentDecls.exists(_.id == intent.id && 
existsForOtherMethod(intent.id)) then
-                    E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin, 
callback=$mtdStr, id=${intent.id}]")
-                else
-                    bindIntent(intent, prepareCallback(mtd, obj, intent), mtd)
-
-    /**
-      *
-      * @param mtd
-      * @param obj
-      */
-    private def processMethodRefs(mtd: Method, obj: Object): Unit =
-        val anns = mtd.getAnnotationsByType(CLS_INTENT_REF)
-
-        for (ann <- anns)
-            val refId = ann.value.strip
-
-            intentDecls.find(_.id == refId) match
-                case Some(intent) => bindIntent(intent, prepareCallback(mtd, 
obj, intent), mtd)
-                case None => E(s"@NCIntentRef(\"$refId\") references unknown 
intent ID [mdlId=$mdlId, origin=$origin, refId=$refId, 
callback=${method2Str(mtd)}]")
-
-    /**
-      *
+      * @param cfg
       * @param method
       * @param obj
       * @param intent
       * @return
       */
-    private def prepareCallback(method: Method, obj: Object, intent: 
NCIDLIntent): CallbackHolder =
+    private def prepareCallback(cfg: NCModelConfig, method: Method, obj: 
Object, intent: NCIDLIntent): NCIntentMatch => NCResult =
+        val mdlId = cfg.getId
+
         // Checks method result type.
         if method.getReturnType != CLS_QRY_RES then
             E(s"Unexpected result type for @NCIntent annotated method 
[mdlId=$mdlId, intentId=${intent.id}, type=${class2Str(method.getReturnType)}, 
callback=${method2Str(method)}]")
@@ -491,7 +492,7 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
                             E(s"Missing @NCIntentTerm annotation for 
[mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]")
 
                     case _ => E(s"Too many @NCIntentTerm annotations for 
[mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]")
-            }
+        }
 
         if NCUtils.containsDups(termIds) then
             E(s"Duplicate term IDs in @NCIntentTerm annotations [mdlId=$mdlId, 
intentId=${intent.id}, dups=${NCUtils.getDups(termIds).mkString(", ")}, 
callback=${method2Str(method)}]")
@@ -510,120 +511,119 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
         val paramGenTypes = 
getSeq(method.getGenericParameterTypes.toIndexedSeq)
 
         // Checks parameters.
-        checkTypes(method, tokParamTypes, paramGenTypes, ctxFirstParam)
+        checkTypes(cfg, method, tokParamTypes, paramGenTypes, ctxFirstParam)
 
         // Checks limits.
         val allLimits = terms.map(t => t.id.orNull -> (t.min, t.max)).toMap
 
-        checkMinMax(method, tokParamTypes, termIds.map(allLimits), 
ctxFirstParam)
+        checkMinMax(cfg, method, tokParamTypes, termIds.map(allLimits), 
ctxFirstParam)
 
-        CallbackHolder(
-            method,
-            (ctx: NCIntentMatch) =>
-                val args = mutable.Buffer.empty[AnyRef]
-                if ctxFirstParam then args += ctx
-                args ++= prepareParams(mdlId, method, tokParamTypes, 
termIds.map(ctx.getTermEntities), ctxFirstParam)
+        (ctx: NCIntentMatch) =>
+            val args = mutable.Buffer.empty[AnyRef]
+            if ctxFirstParam then args += ctx
+            args ++= prepareParams(cfg, method, tokParamTypes, 
termIds.map(ctx.getTermEntities), ctxFirstParam)
 
-                invoke(mdlId, method, obj, args.toArray)
-        )
+            invoke(cfg, method, obj, args.toArray)
 
     /**
       *
-      * @param mtd
       * @return
       */
-    private def scanSamples(mtd: Method): Map[String, Seq[Seq[String]]] =
-        val smpAnns = mtd.getAnnotationsByType(CLS_SAMPLE)
-        val smpAnnsRef = mtd.getAnnotationsByType(CLS_SAMPLE_REF)
-        lazy val mtdStr = method2Str(mtd)
-        lazy val intAnns = mtd.getAnnotationsByType(CLS_INTENT)
-        lazy val refAnns = mtd.getAnnotationsByType(CLS_INTENT_REF)
-        lazy val samples = mutable.HashMap.empty[String, Seq[Seq[String]]]
-
-        if smpAnns.nonEmpty || smpAnnsRef.nonEmpty then
-            if intAnns.isEmpty && refAnns.isEmpty then
-                E(s"@NCIntentSample or @NCIntentSampleRef annotations without 
corresponding @NCIntent or @NCIntentRef annotations: $mtdStr")
-            else
-                def read[T](annArr: scala.Array[T], annName: String, 
getSamples: T => Seq[String], getSource: Option[T => String]): Seq[Seq[String]] 
=
-                    for (ann <- annArr.toSeq) yield
-                        val samples = getSamples(ann).map(_.strip).filter(s => 
s.nonEmpty && s.head != '#')
-
-                        if samples.isEmpty then
-                            getSource match
-                                case None => logger.warn(s"$annName annotation 
has no samples: $mtdStr")
-                                case Some(f) => logger.warn(s"$annName 
annotation references '${f(ann)}' file that has no samples: $mtdStr")
-
-                            Seq.empty
-                        else
-                            samples
-                .filter(_.nonEmpty)
-
-                val seqSeq =
-                    read[NCIntentSample](
-                        smpAnns, "@NCIntentSample", _.value.toSeq, None
-                    ) ++
-                    read[NCIntentSampleRef](
-                        smpAnnsRef, "@NCIntentSampleRef", a => 
NCUtils.readResource(a.value), Option(_.value)
-                    )
-
-                if NCUtils.containsDups(seqSeq.flatMap(_.toSeq).toList) then
-                    logger.warn(s"@NCIntentSample and @NCIntentSampleRef 
annotations have duplicates: $mtdStr")
-
-                val distinct = seqSeq.map(_.distinct).distinct
-
-                for (ann <- intAnns; intent <- 
NCIDLCompiler.compile(ann.value, cfg, mtdStr))
-                    samples += intent.id -> distinct
-
-                for (ann <- refAnns) samples += ann.value -> distinct
-        else if intAnns.nonEmpty || refAnns.nonEmpty then
-            logger.warn(s"@NCIntentSample or @NCIntentSampleRef annotations 
are missing for: $mtdStr")
-
-        samples.toMap
-
-    /**
-      *
-      * @return
-      */
-    def scan(): Seq[NCModelIntent] =
-        def processClassHierarchy(claxx: Class[_]): Unit =
-            if claxx != null then
+    def scan(mdl: NCModel): Seq[NCModelIntent] =
+        val cfg = mdl.getConfig
+        val mdlId = cfg.getId
+        val origin = cfg.getOrigin
+
+        val intents = mutable.Buffer.empty[IntentHolder]
+        val intentDecls = mutable.HashMap.empty[String, NCIDLIntent]
+        val objs = mutable.Buffer.empty[Object]
+        val parents = mutable.HashSet.empty[Class[_]]
+        val samples = mutable.HashMap.empty[Method, Map[String, 
Seq[Seq[String]]]]
+
+        def processClass(claxx: Class[_]): Unit =
+            if claxx != null && parents.add(claxx) then
                 val anns = claxx.getAnnotationsByType(CLS_INTENT)
 
                 if anns.nonEmpty then
                     val origin = getClassName(claxx)
 
                     for (ann <- anns; intent <- 
NCIDLCompiler.compile(ann.value, cfg, origin))
-                        // TODO: duplicate
-                        intentDecls += intent
+                        intentDecls += intent.id -> intent
 
-                processClassHierarchy(claxx.getSuperclass)
-                claxx.getInterfaces.foreach(processClassHierarchy)
+                processClass(claxx.getSuperclass)
+                claxx.getInterfaces.foreach(processClass)
 
-        // 1. First phase scan.
+        // First phase scan.
         //  - For given object finds references via fields (NCIntentObject). 
Scans also each reference recursively and collects them.
         //  - For all methods of processed object collects samples 
(NCIntentSample, NCIntentSampleRef) and intents (NCIntent)
         def scan(obj: Object): Unit =
             objs += obj
 
-            processClassHierarchy(obj.getClass)
+            processClass(obj.getClass)
+
+            for (mtd <- getAllMethods(obj))
+                samples += mtd -> scanSamples(cfg, mtd)
+
+                for (
+                    ann <- mtd.getAnnotationsByType(CLS_INTENT);
+                    intent <- NCIDLCompiler.compile(ann.value, cfg, 
method2Str(mtd))
+                )
+                    intentDecls += intent.id -> intent
+                    intents += IntentHolder(cfg, intent, obj, mtd)
 
-            for (m <- getAllMethods(obj)) processMethod(m, obj)
-            for (f <- getAllFields(obj) if 
f.isAnnotationPresent(CLS_INTENT_OBJ)) scan(getFieldObject(mdlId, f, obj))
+            for (f <- getAllFields(obj) if 
f.isAnnotationPresent(CLS_INTENT_OBJ))
+                scan(getFieldObject(cfg, f, obj))
 
         scan(mdl)
 
-        // 2. For model and all its references scans each method and finds 
intents references (NCIntentRef)
-        for (o <- objs; m <- getAllMethods(o)) processMethodRefs(m, o)
+        // Second phase. For model and all its references scans each method 
and finds intents references (NCIntentRef)
+        for (
+            obj <- objs;
+            mtd <- getAllMethods(obj);
+            ann <- mtd.getAnnotationsByType(CLS_INTENT_REF)
+        )
+            val refId = ann.value.strip
+
+            val intent = intentDecls.getOrElse(
+                refId,
+                E(s"@NCIntentRef(\"$refId\") references unknown intent ID 
[mdlId=$mdlId, origin=$origin, callback=${method2Str(mtd)}]")
+            )
+
+            intentDecls += intent.id -> intent
+            intents += IntentHolder(cfg, intent, obj, mtd)
+
+        // Validation.
+        // IDL with same ID but different IDL bodies.
+        val duplDefs: Set[NCIDLIntent] =
+            intentDecls.values.groupBy(_.id).
+                map { (id, seq) => id -> seq.map(_.idl).toSet }.
+                filter { (_, seq) => seq.sizeIs > 1 }.
+                keySet.
+                map(intentDecls)
+
+        def str(iter: Iterable[_]): String = iter.mkString("{", ",", "}")
+
+        if duplDefs.nonEmpty then
+            val s = str(duplDefs.map(p => s"id=${p.id}, origin=${p.origin}"))
+            E(s"Following IDL has same identifiers with different bodies: $s")
+
+        val duplInts: Map[Method, Seq[String]] =
+            intents.groupBy(_.method).
+                map { case (mtd, seq) => mtd -> seq.map(_.intent.id).toSeq }.
+                filter { case (_, seq) => seq.sizeIs > 1 }
+
+        if duplInts.nonEmpty then
+            val s = str(duplInts.map { (mtd, idlIds) => 
s"method=${method2Str(mtd)}, intents=${str(idlIds)}" })
+            E(s"Some methods have more that one intent: $s")
 
-        // 3. Validation.
-        val unusedIntents = intentDecls.filter(i => !intents.exists(_._1.id == 
i.id))
+        val unusedIds = intentDecls.keys.filter(k => 
!intents.exists(_.intent.id == k))
 
-        if unusedIntents.nonEmpty then
-            logger.warn(s"Intents are unused (have no callback): 
[mdlId=$mdlId, origin=$origin, 
intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]")
+        if unusedIds.nonEmpty then
+            logger.warn(s"Intents are unused (have no callback): 
[mdlId=$mdlId, origin=$origin, intentIds=${unusedIds.mkString("(", ", ", 
")")}]")
 
         if intents.nonEmpty then
-        // Check the uniqueness of intent IDs.
-            NCUtils.getDups(intents.map(_._1).toSeq.map(_.id)) match
+            // Check the uniqueness of intent IDs.
+            NCUtils.getDups(intents.map(_.intent.id).toSeq) match
                 case ids if ids.nonEmpty => E(s"Duplicate intent IDs 
[mdlId=$mdlId, origin=$origin, ids=${ids.mkString(",")}]")
                 case _ => // No-op.
         else
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
index 0640627..0c1e2dd 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
@@ -102,12 +102,12 @@ class NCModelIntentsInvalidArgsSpec:
         mkResult0(list)
 
     private def testOk(mdl: NCModel, intentId: String): Unit =
-        val i = new NCModelScanner(mdl).scan().find(_.intent.id == 
intentId).get
+        val i = NCModelScanner.scan(mdl).find(_.intent.id == intentId).get
 
         println(s"Test finished [modelClass=${mdl.getClass}, intent=$intentId, 
result=${i.function(INTENT_MATCH)}")
 
     private def testRuntimeClassCast(mdl: NCModel, intentId: String): Unit =
-        val i = new NCModelScanner(mdl).scan().find(_.intent.id == 
intentId).get
+        val i = NCModelScanner.scan(mdl).find(_.intent.id == intentId).get
 
         try
             i.function(INTENT_MATCH)
@@ -123,7 +123,7 @@ class NCModelIntentsInvalidArgsSpec:
 
     private def testScanValidation(mdl: NCModel): Unit =
         try
-            new NCModelScanner(mdl).scan()
+            NCModelScanner.scan(mdl)
 
             require(false)
         catch
@@ -133,14 +133,14 @@ class NCModelIntentsInvalidArgsSpec:
 
     @Test
     def test(): Unit =
-        testOk(CHECKED_MDL_VALID, "validList")
-        testOk(CHECKED_MDL_VALID, "validOpt")
+//        testOk(CHECKED_MDL_VALID, "validList")
+//        testOk(CHECKED_MDL_VALID, "validOpt")
 
         // Errors thrown on scan phase if error found in any intent.
         testScanValidation(CHECKED_MDL_INVALID_LST)
-        testScanValidation(CHECKED_MDL_INVALID_OPT)
-
-        testOk(UNCHECKED_MDL, "validList")
-        testOk(UNCHECKED_MDL, "validOpt")
-        testRuntimeClassCast(UNCHECKED_MDL, "invalidList")
-        testRuntimeClassCast(UNCHECKED_MDL, "invalidOpt")
\ No newline at end of file
+//        testScanValidation(CHECKED_MDL_INVALID_OPT)
+//
+//        testOk(UNCHECKED_MDL, "validList")
+//        testOk(UNCHECKED_MDL, "validOpt")
+//        testRuntimeClassCast(UNCHECKED_MDL, "invalidList")
+//        testRuntimeClassCast(UNCHECKED_MDL, "invalidOpt")
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
index c8e2a4c..e025bbb 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
@@ -81,13 +81,13 @@ class NCModelIntentsNestedSpec:
 
     @Test
     def test(): Unit =
-        require(new NCModelScanner(MDL_VALID1).scan().sizeIs == 4)
-        require(new NCModelScanner(MDL_VALID2).scan().sizeIs == 4)
+        require(NCModelScanner.scan(MDL_VALID1).sizeIs == 4)
+        require(NCModelScanner.scan(MDL_VALID2).sizeIs == 4)
 
     @Test
     def testNull(): Unit =
         try
-            new NCModelScanner(MDL_INVALID).scan()
+            NCModelScanner.scan(MDL_INVALID)
 
             require(false)
         catch
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
index 36ce1e6..e26fe6e 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
@@ -34,5 +34,5 @@ class NCModelIntentsSpec:
     )
 
     @Test
-    def test(): Unit = for (mdl <- mdls) require(new 
NCModelScanner(mdl).scan().nonEmpty)
+    def test(): Unit = for (mdl <- mdls) 
require(NCModelScanner.scan(mdl).nonEmpty)
 

Reply via email to