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 7beb383  WIP.
7beb383 is described below

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

    WIP.
---
 .../nlpcraft/internal/impl/NCModelScanner.scala    | 181 ++++++++++-----------
 .../scan/NCModelIntentsInvalidIntentsSpec.scala    | 124 ++++++++++++++
 .../impl/scan/NCModelIntentsNestedSpec.scala       |   2 +-
 3 files changed, 207 insertions(+), 100 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 77dcee8..723ab75 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
@@ -92,24 +92,24 @@ object NCModelScanner extends LazyLogging:
 
     /**
       *
-      * @param cls
+      * @param wct
       * @return
       */
-    private def class2Str(cls: Class[_]): String = if cls == null then "null" 
else s"'${cls.getSimpleName}'"
+    private def wc2Str(wct: WildcardType): String = if wct == null then "null" 
else s"'${wct.getTypeName}'"
 
     /**
       *
-      * @param wct
+      * @param iter
       * @return
       */
-    private def wc2Str(wct: WildcardType): String = if wct == null then "null" 
else s"'${wct.getTypeName}'"
+    def col2Str(iter: Iterable[_]): String = iter.mkString("(", ",", ")")
 
     /**
       *
       * @param clazz
       * @return
       */
-    private def getClassName(clazz: Class[_]): String =
+    private def class2Str(clazz: Class[_]): String =
         val cls = clazz.getSimpleName.strip
         // Anonymous classes (like `class foo.bar.name$1`) doesn't have simple 
names.
         if cls.nonEmpty then cls else clazz.getName.reverse.takeWhile(_ != 
'.').reverse
@@ -120,7 +120,7 @@ object NCModelScanner extends LazyLogging:
       * @return
       */
     private def method2Str(mtd: Method): String =
-        val cls = getClassName(mtd.getDeclaringClass)
+        val cls = class2Str(mtd.getDeclaringClass)
         val name = mtd.getName
         val args = mtd.getParameters.map(_.getType.getSimpleName).mkString(", 
")
 
@@ -132,7 +132,7 @@ object NCModelScanner extends LazyLogging:
       * @return
       */
     private def field2Str(f: Field): String =
-        val cls = getClassName(f.getDeclaringClass)
+        val cls = class2Str(f.getDeclaringClass)
         val name = f.getName
 
         s"$cls.$name"
@@ -147,8 +147,6 @@ object NCModelScanner extends LazyLogging:
       * @return
       */
     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)
 
@@ -157,7 +155,7 @@ object NCModelScanner extends LazyLogging:
             // Single entity.
             if paramCls == CLS_ENTITY then
                 if entsCnt != 1 then
-                    E(s"Expected single entity (found $entsCnt) in 
@NCIntentTerm annotated argument [mdlId=$mdlId, arg=${mkArg()}]")
+                    E(s"Expected single entity (found $entsCnt) in 
@NCIntentTerm annotated argument [mdlId=${cfg.getId}, arg=${mkArg()}]")
 
                 argList.get(0)
             // Array of entities.
@@ -175,15 +173,15 @@ object NCModelScanner extends LazyLogging:
                 entsCnt match
                     case 0 => None
                     case 1 => Option(argList.get(0))
-                    case _ => E(s"Too many entities ($entsCnt) for 
scala.Option[_] @NCIntentTerm annotated argument [mdlId=$mdlId, 
arg=${mkArg()}]")
+                    case _ => E(s"Too many entities ($entsCnt) for 
scala.Option[_] @NCIntentTerm annotated argument [mdlId=${cfg.getId}, 
arg=${mkArg()}]")
             else if paramCls == CLS_JAVA_OPT then
                 entsCnt match
                     case 0 => util.Optional.empty()
                     case 1 => util.Optional.of(argList.get(0))
-                    case _ => E(s"Too many entities ($entsCnt) for 
java.util.Optional @NCIntentTerm annotated argument [mdlId=$mdlId, 
arg=${mkArg()}]")
+                    case _ => E(s"Too many entities ($entsCnt) for 
java.util.Optional @NCIntentTerm annotated argument [mdlId=${cfg.getId}, 
arg=${mkArg()}]")
             else
                 // All allowed arguments types already checked.
-                throw new AssertionError(s"Unexpected callback @NCIntentTerm 
argument type [mdlId=$mdlId, type=$paramCls, arg=${mkArg()}]")
+                throw new AssertionError(s"Unexpected callback @NCIntentTerm 
argument type [mdlId=${cfg.getId}, type=$paramCls, arg=${mkArg()}]")
         }
 
     /**
@@ -195,7 +193,6 @@ object NCModelScanner extends LazyLogging:
       * @return
       */
     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)
 
@@ -214,15 +211,15 @@ object NCModelScanner extends LazyLogging:
                     case cause: NCIntentSkip => throw cause
                     case cause: NCRejection => throw cause
                     case cause: NCException => throw cause
-                    case cause: Throwable => E(s"Intent callback invocation 
error [mdlId=$mdlId, callback=${method2Str(method)}]", cause)
+                    case cause: Throwable => E(s"Intent callback invocation 
error [mdlId=${cfg.getId}, callback=${method2Str(method)}]", cause)
 
-            case e: Throwable => E(s"Unexpected intent callback invocation 
error [mdlId=$mdlId, callback=${method2Str(method)}]", e)
+            case e: Throwable => E(s"Unexpected intent callback invocation 
error [mdlId=${cfg.getId}, callback=${method2Str(method)}]", e)
         finally
             if flag then
                 try
                     method.setAccessible(false)
                 catch
-                    case e: SecurityException => E(s"Access or security error 
in intent callback [mdlId=$mdlId, callback=${method2Str(method)}]", e)
+                    case e: SecurityException => E(s"Access or security error 
in intent callback [mdlId=${cfg.getId}, callback=${method2Str(method)}]", e)
 
     /**
       *
@@ -232,7 +229,6 @@ object NCModelScanner extends LazyLogging:
       * @return
       */
     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)
@@ -248,14 +244,14 @@ object NCModelScanner extends LazyLogging:
                 field.get(fieldObj)
             catch
                 // TODO: text
-                case e: Throwable => E(s"Unexpected field access error 
[mdlId=$mdlId, field=$fStr]", e)
+                case e: Throwable => E(s"Unexpected field access error 
[mdlId=${cfg.getId}, field=$fStr]", e)
             finally
                 if flag then
                     try
                         field.setAccessible(false)
                     catch
                         // TODO: text
-                        case e: SecurityException => E(s"Access or security 
error in field [mdlId=$mdlId, field=$fStr]", e)
+                        case e: SecurityException => E(s"Access or security 
error in field [mdlId=${cfg.getId}, field=$fStr]", e)
 
         if res == null then
             throw new NCException(s"Value is null for: $fStr") // TODO: text
@@ -303,6 +299,7 @@ object NCModelScanner extends LazyLogging:
         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
@@ -327,9 +324,9 @@ object NCModelScanner extends LazyLogging:
                     read[NCIntentSample](
                         smpAnns, "@NCIntentSample", _.value.toSeq, None
                     ) ++
-                        read[NCIntentSampleRef](
-                            smpAnnsRef, "@NCIntentSampleRef", a => 
NCUtils.readResource(a.value), Option(_.value)
-                        )
+                    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")
@@ -354,9 +351,6 @@ object NCModelScanner extends LazyLogging:
       * @param ctxFirstParam
       */
     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
@@ -370,7 +364,7 @@ object NCModelScanner extends LazyLogging:
                 val compType = argClass.getComponentType
 
                 if compType != CLS_ENTITY then
-                    E(s"Unexpected array element type for @NCIntentTerm 
annotated argument [mdlId=$mdlId, origin=$origin, type=${class2Str(compType)}, 
arg=${mkArg()}]")
+                    E(s"Unexpected array element type for @NCIntentTerm 
annotated argument [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
type=${class2Str(compType)}, arg=${mkArg()}]")
                 // Entities collection and optionals.
             else if COMP_CLS.contains(argClass) then
                 paramGenType match
@@ -379,7 +373,7 @@ object NCModelScanner extends LazyLogging:
                         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()}]")
+                            E(s"Unexpected generic types count for 
@NCIntentTerm annotated argument [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
count=${compTypes.length}, arg=${mkArg()}]")
 
                         val compType = compTypes.head
 
@@ -388,7 +382,7 @@ object NCModelScanner extends LazyLogging:
                             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()}]")
+                                    E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
type=${class2Str(genClass)}, arg=${mkArg()}]")
 
                             // Kotlin.
                             case _: WildcardType =>
@@ -396,9 +390,9 @@ object NCModelScanner extends LazyLogging:
                                 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()}]")
+                                    E(s"Unexpected Kotlin generic type for 
@NCIntentTerm annotated argument [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
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 _ => E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
type=${compType.getTypeName}, arg=${mkArg()}]")
                     case _ =>
                         // Scala.
                         if COMP_CLS.exists(_ == paramGenType) then
@@ -407,10 +401,10 @@ object NCModelScanner extends LazyLogging:
                                 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()}]")
+                            E(s"Unexpected parameter type for @NCIntentTerm 
annotated argument [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
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()}]")
+                E(s"Unexpected parameter type for @NCIntentTerm annotated 
argument [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 
type=${class2Str(argClass)}, arg=${mkArg()}]")
         }
 
     /**
@@ -453,11 +447,9 @@ object NCModelScanner extends LazyLogging:
       * @return
       */
     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)}]")
+            E(s"Unexpected result type for @NCIntent annotated method 
[mdlId=${cfg.getId}, intentId=${intent.id}, 
type=${class2Str(method.getReturnType)}, callback=${method2Str(method)}]")
 
         val allParamTypes = method.getParameterTypes.toSeq
         val ctxFirstParam = allParamTypes.nonEmpty && allParamTypes.head == 
CLS_INTENT_MATCH
@@ -473,7 +465,7 @@ object NCModelScanner extends LazyLogging:
 
         // Checks entities parameters annotations count.
         if tokParamAnns.sizeIs != tokParamTypes.length then
-            E(s"Unexpected annotations count for @NCIntent annotated method 
[mdlId=$mdlId, intentId=${intent.id}, count=${tokParamAnns.size}, 
callback=${method2Str(method)}]")
+            E(s"Unexpected annotations count for @NCIntent annotated method 
[mdlId=${cfg.getId}, intentId=${intent.id}, count=${tokParamAnns.size}, 
callback=${method2Str(method)}]")
 
         // Gets terms IDs.
         val termIds = tokParamAnns.toList.zipWithIndex.map {
@@ -487,15 +479,15 @@ object NCModelScanner extends LazyLogging:
                     case 1 => termAnns.head.asInstanceOf[NCIntentTerm].value
                     case 0 =>
                         if idx == 0 then
-                            E(s"Missing @NCIntentTerm annotation or wrong type 
of the 1st parameter (must be 'NCIntentMatch') for [mdlId=$mdlId, 
intentId=${intent.id}, arg=${mkArg()}]")
+                            E(s"Missing @NCIntentTerm annotation or wrong type 
of the 1st parameter (must be 'NCIntentMatch') for [mdlId=${cfg.getId}, 
intentId=${intent.id}, arg=${mkArg()}]")
                         else
-                            E(s"Missing @NCIntentTerm annotation for 
[mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]")
+                            E(s"Missing @NCIntentTerm annotation for 
[mdlId=${cfg.getId}, intentId=${intent.id}, arg=${mkArg()}]")
 
-                    case _ => E(s"Too many @NCIntentTerm annotations for 
[mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]")
+                    case _ => E(s"Too many @NCIntentTerm annotations for 
[mdlId=${cfg.getId}, 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)}]")
+            E(s"Duplicate term IDs in @NCIntentTerm annotations 
[mdlId=${cfg.getId}, intentId=${intent.id}, 
dups=${NCUtils.getDups(termIds).mkString(", ")}, 
callback=${method2Str(method)}]")
 
         val terms = intent.terms
 
@@ -506,7 +498,7 @@ object NCModelScanner extends LazyLogging:
 
         if invalidIds.nonEmpty then
             // Report only the first one for simplicity & clarity.
-            E(s"Unknown term ID in @NCIntentTerm annotation [mdlId=$mdlId, 
intentId=${intent.id}, termId=${invalidIds.head}, 
callback=${method2Str(method)}]")
+            E(s"Unknown term ID in @NCIntentTerm annotation 
[mdlId=${cfg.getId}, intentId=${intent.id}, termId=${invalidIds.head}, 
callback=${method2Str(method)}]")
 
         val paramGenTypes = 
getSeq(method.getGenericParameterTypes.toIndexedSeq)
 
@@ -531,48 +523,64 @@ object NCModelScanner extends LazyLogging:
       */
     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 processed = 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)
+        def addDecl(intent: NCIDLIntent): Unit =
+            intentDecls.get(intent.id) match {
+                case Some(ex) =>
+                    if ex.idl != intent.idl then
+                        // TODO: text
+                        E(s"Intent with given ID already found with different 
definition [mdlId=${cfg.getId}, origin=${cfg.getOrigin}, id=${intent.id}]")
+                case None => // No-op.
+            }
+
+            intentDecls += intent.id -> intent
 
-                if anns.nonEmpty then
-                    val origin = getClassName(claxx)
+        def addIntent(intent: NCIDLIntent, mtd: Method, obj: Object): Unit =
+            if intents.exists(_.method == mtd) then
+                // TODO: text
+                E(s"The callback cannot have more one intent 
[mdlId=${cfg.getId}, origin=${cfg.getOrigin}, callback=${method2Str(mtd)}]")
+
+            intents += IntentHolder(cfg, intent, obj, mtd)
 
-                    for (ann <- anns; intent <- 
NCIDLCompiler.compile(ann.value, cfg, origin))
-                        intentDecls += intent.id -> intent
+        def processClassAnnotations(claxx: Class[_]): Unit =
+            if claxx != null && processed.add(claxx) then
+                for (
+                    ann <- claxx.getAnnotationsByType(CLS_INTENT);
+                    intent <- NCIDLCompiler.compile(ann.value, cfg, 
class2Str(claxx))
+                )
+                    addDecl(intent)
 
-                processClass(claxx.getSuperclass)
-                claxx.getInterfaces.foreach(processClass)
+                processClassAnnotations(claxx.getSuperclass)
+                claxx.getInterfaces.foreach(processClassAnnotations)
 
         // 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)
+        // 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
+            processClassAnnotations(obj.getClass)
+            val methods = getAllMethods(obj)
 
-            processClass(obj.getClass)
+            // Collects samples for each method.
+            for (mtd <- methods) samples += mtd -> scanSamples(cfg, mtd)
 
-            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)
+            // // Collects intents for each method.
+            for (
+                mtd <- methods;
+                ann <- mtd.getAnnotationsByType(CLS_INTENT);
+                intent <- NCIDLCompiler.compile(ann.value, cfg, 
method2Str(mtd))
+            )
+                addDecl(intent)
+                addIntent(intent, mtd, obj)
 
-            for (f <- getAllFields(obj) if 
f.isAnnotationPresent(CLS_INTENT_OBJ))
-                scan(getFieldObject(cfg, f, obj))
+            // Scans annotated fields.
+            for (f <- getAllFields(obj) if 
f.isAnnotationPresent(CLS_INTENT_OBJ)) scan(getFieldObject(cfg, f, obj))
 
         scan(mdl)
 
@@ -583,50 +591,25 @@ object NCModelScanner extends LazyLogging:
             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)}]")
+                E(s"@NCIntentRef(\"$refId\") references unknown intent ID 
[mdlId=${cfg.getId}, origin=${cfg.getOrigin}, 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")
+            addDecl(intent)
+            addIntent(intent, mtd, obj)
 
         val unusedIds = intentDecls.keys.filter(k => 
!intents.exists(_.intent.id == k))
 
         if unusedIds.nonEmpty then
-            logger.warn(s"Intents are unused (have no callback): 
[mdlId=$mdlId, origin=$origin, intentIds=${unusedIds.mkString("(", ", ", 
")")}]")
+            logger.warn(s"Intents are unused (have no callback): 
[mdlId=${cfg.getId}, origin=${cfg.getOrigin}, intentIds=${col2Str(unusedIds)}]")
 
         if intents.nonEmpty then
             // 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 ids if ids.nonEmpty => E(s"Duplicate intent IDs 
[mdlId=${cfg.getId}, origin=${cfg.getOrigin}, ids=${col2Str(ids)}]")
                 case _ => // No-op.
         else
-            logger.warn(s"Model has no intent: $mdlId")
+            logger.warn(s"Model has no intent: ${cfg.getId}")
 
         intents.map(i => NCModelIntent(i.intent, i.function, 
samples.getOrElse(i.method, Map.empty).getOrElse(i.intent.id, Seq.empty))).toSeq
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
new file mode 100644
index 0000000..b23dc84
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.impl.scan
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.internal.impl.NCModelScanner
+import org.apache.nlpcraft.nlp.util.*
+import org.apache.nlpcraft.nlp.util.opennlp.*
+import org.junit.jupiter.api.Test
+
+import java.util
+
+/**
+  * It tests invalid intents definition.
+  */
+class NCModelIntentsInvalidIntentsSpec:
+    private def testError(mdl: NCModel): Unit =
+        try
+            NCModelScanner.scan(mdl)
+
+            require(false)
+        catch
+            case e: NCException =>
+                println("Expected stack trace:")
+                e.printStackTrace(System.out)
+
+    /**
+      * Two intents on one method.
+      */
+    @Test
+    def testError1(): Unit =
+        testError(
+            new NCTestModelAdapter():
+                @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+                @NCIntent("intent=validList2 term(list)~{# == 'x'}[0,10]")
+                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+        )
+
+    /**
+      * Invalid reference.
+      */
+    @Test
+    def testError2(): Unit =
+        testError(
+            new NCTestModelAdapter():
+                @NCIntentRef("missed")
+                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+        )
+
+    /**
+      * Samples without intent.
+      */
+    @Test
+    def testError3(): Unit =
+        testError(
+            new NCTestModelAdapter():
+                @NCIntentSample(Array("sample"))
+                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+        )
+
+    /**
+      * Duplicated intents definitions.
+      */
+    @Test
+    def testError4(): Unit =
+        @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+        @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,11]")
+        class X:
+            val x = 1
+
+        testError(
+            new NCTestModelAdapter():
+                @NCIntentObject
+                val x = new X()
+        )
+
+    /**
+      * Invalid argument type.
+      */
+    @Test
+    def testError5(): Unit =
+        testError(
+            new NCTestModelAdapter():
+                @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+                def x(@NCIntentTerm("list") e: NCEntity): NCResult = null
+        )
+
+    /**
+      * Duplicated samples definitions.
+      */
+    @Test
+    def testWarning1(): Unit =
+        NCModelScanner.scan(
+            new NCTestModelAdapter():
+                @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+                @NCIntentSample(Array("x", "x"))
+                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+        )
+
+    /**
+      * Missed samples.
+      */
+    @Test
+    def testWarning2(): Unit =
+        NCModelScanner.scan(
+            new NCTestModelAdapter():
+                @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+        )
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 e025bbb..91e2f09 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
@@ -93,4 +93,4 @@ class NCModelIntentsNestedSpec:
         catch
             case e: NCException =>
                 println("Expected stack trace:")
-                e.printStackTrace(System.out)
+                e.printStackTrace(System.out)
\ No newline at end of file

Reply via email to