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

commit ca8378a3ff8a3a5f66dc44b4b8df8344482b72b1
Author: Sergey Kamov <skhdlem...@gmail.com>
AuthorDate: Thu Jan 27 15:20:32 2022 +0300

    WIP.
---
 .../internal/impl/NCAnnotationsScanner.scala       | 223 ++++++++++++---------
 nlpcraft/src/test/resources/scan/idl.idl           |   2 +-
 .../nlpcraft/internal/impl/NCTestModelScala.scala  |  67 -------
 .../impl/scan/NCAnnotationInvalidArgsSpec.scala    | 159 +++++++++++++++
 .../impl/scan/NCAnnotationNestedSpec.scala         |  58 ++++++
 .../NCAnnotationSpec.scala}                        |  11 +-
 .../internal/impl/{ => scan}/NCTestModelJava.java  |  14 +-
 .../internal/impl/scan/NCTestModelScala.scala      |  88 ++++++++
 .../intent/compiler/functions/NCIDLFunctions.scala |  16 +-
 .../apache/nlpcraft/nlp/util/NCTestEntity.scala    |  47 +++++
 10 files changed, 502 insertions(+), 183 deletions(-)

diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala
index 3625dc7..e1a1b46 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala
@@ -18,6 +18,7 @@
 package org.apache.nlpcraft.internal.impl
 
 import com.typesafe.scalalogging.LazyLogging
+import org.apache.commons.lang3.reflect.FieldUtils
 import org.apache.nlpcraft.*
 import org.apache.nlpcraft.internal.intent.*
 import org.apache.nlpcraft.internal.intent.compiler.*
@@ -43,7 +44,7 @@ private[internal] case class NCCallback(method: Method, 
cbFun: Function[NCIntent
 private[internal] case class NCIntentData(intent: NCIDLIntent, callback: 
NCCallback, samples: Seq[Seq[String]])
 
 /**
-  *
+  * TODO: common comment for annotations usage. Do not apply annotations for 
unused private methods and fields. Compiler can drop them.
   */
 object NCAnnotationsScanner extends LazyLogging:
     private final val SEPARATORS = Seq('?', ',', '.', '-', '!')
@@ -76,16 +77,6 @@ object NCAnnotationsScanner extends LazyLogging:
 
     /**
       *
-      * @param method
-      * @param claxx
-      * @param obj
-      */
-    private case class MethodOwner(method: Method, obj: Any):
-        require(method != null && obj != null)
-        lazy val className: Class[_] = obj.getClass
-
-    /**
-      *
       * @param cls
       * @return
       */
@@ -100,11 +91,21 @@ object NCAnnotationsScanner extends LazyLogging:
 
     /**
       *
+      * @param clazz
+      * @return
+      */
+    private def getClassName(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
+
+    /**
+      *
       * @param mtd
       * @return
       */
     private def method2Str(mtd: Method): String =
-        val cls = mtd.getDeclaringClass.getSimpleName
+        val cls = getClassName(mtd.getDeclaringClass)
         val name = mtd.getName
         val args = mtd.getParameters.map(_.getType.getSimpleName).mkString(", 
")
 
@@ -116,7 +117,7 @@ object NCAnnotationsScanner extends LazyLogging:
       * @return
       */
     private def field2Str(f: Field): String =
-        val cls = f.getDeclaringClass.getSimpleName
+        val cls = getClassName(f.getDeclaringClass)
         val name = f.getName
 
         s"$cls.$name"
@@ -164,45 +165,77 @@ object NCAnnotationsScanner extends LazyLogging:
                     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()}]")
             else
-                // All allowed arguments types already checked, but // TODO: 
(spec case)
-                throw new NCException(s"Unexpected callback @NCIntentTerm 
argument type [mdlId=$mdlId, type=$paramCls, arg=${mkArg()}]")
+                // All allowed arguments types already checked.
+                throw new AssertionError(s"Unexpected callback @NCIntentTerm 
argument type [mdlId=$mdlId, type=$paramCls, arg=${mkArg()}]")
         }
 
     /**
       *
       * @param mdlId
-      * @param mo
+      * @param method
+      * @param obj
       * @param args
       * @return
       */
-    private def invoke(mdlId: String, mo: MethodOwner, args: 
scala.Array[AnyRef]): NCResult =
-        val obj = if Modifier.isStatic(mo.method.getModifiers) then null else 
mo.obj
-        var flag = mo.method.canAccess(obj)
+    private def invoke(mdlId: String, method: Method, obj: Object, args: 
scala.Array[AnyRef]): NCResult =
+        val methodObj = if Modifier.isStatic(method.getModifiers) then null 
else obj
+        var flag = method.canAccess(methodObj)
 
         try
             if !flag then
-                mo.method.setAccessible(true)
+                method.setAccessible(true)
 
                 flag = true
             else
                 flag = false
 
-            mo.method.invoke(obj, args: _*).asInstanceOf[NCResult]
+            method.invoke(methodObj, args: _*).asInstanceOf[NCResult]
         catch
             case e: InvocationTargetException =>
                 e.getTargetException match
                     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(mo.method)}]", cause)
+                    case cause: Throwable => E(s"Intent callback invocation 
error [mdlId=$mdlId, callback=${method2Str(method)}]", cause)
+
+            case e: Throwable => E(s"Unexpected intent callback invocation 
error [mdlId=$mdlId, 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)
+
+    /**
+      *
+      * @param mdlId
+      * @param field
+      * @param obj
+      * @return
+      */
+    private def getField(mdlId: String, field: Field, obj: Object): Object =
+        val fieldObj = if Modifier.isStatic(field.getModifiers) then null else 
obj
+        var flag = field.canAccess(fieldObj)
+
+        try
+            if !flag then
+                field.setAccessible(true)
+
+                flag = true
+            else
+                flag = false
 
-            case e: Throwable => E(s"Unexpected intent callback invocation 
error [mdlId=$mdlId, callback=${method2Str(mo.method)}]", e)
+            field.get(fieldObj)
+        catch
+            // TODO: text
+            case e: Throwable => E(s"Unexpected field access error 
[mdlId=$mdlId, field=${field2Str(field)}]", e)
         finally
             if flag then
                 try
-                    mo.method.setAccessible(false)
+                    field.setAccessible(false)
                 catch
-                    case e: SecurityException => E(s"Access or security error 
in intent callback [mdlId=$mdlId, callback=${method2Str(mo.method)}]", e)
+                    // TODO: text
+                    case e: SecurityException => E(s"Access or security error 
in field [mdlId=$mdlId, field=${field2Str(field)}]", e)
 
     /**
       *
@@ -305,7 +338,7 @@ import 
org.apache.nlpcraft.internal.impl.NCAnnotationsScanner.*
   */
 class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging:
     private val cfg = mdl.getConfig
-    private val id = cfg.getId
+    private val mdlId = cfg.getId
     private val origin = cfg.getOrigin
 
     /**
@@ -325,7 +358,7 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
                 intent <- 
NCIDLCompiler.compile(NCUtils.readResource(res.strip).mkString("\n"), cfg, res)
             )
                 if intentDecls.exists(_.id == intent.id) then
-                    E(s"Duplicate intent ID [mdlId=$id, origin=$origin, 
resource=$res, id=${intent.id}]")
+                    E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin, 
resource=$res, id=${intent.id}]")
                 intentDecls += intent
 
         def scanClass(obj: Object): Unit =
@@ -339,7 +372,7 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
             for (f <- getAllFields(claxx))
                 processImports(f.getAnnotationsByType(CLS_INTENT_IMPORT), 
field2Str(f))
                 if (f.isAnnotationPresent(CLS_INTENT_OBJ))
-                    val ref = f.get(obj)
+                    val ref = getField(mdlId, f, obj)
                     objs += ref
                     scanClass(ref)
 
@@ -358,67 +391,68 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
 
         // Model instance methods processed.
         for (m <- getAllMethods(mdl))
-            processMethod(intentDecls, intents, MethodOwner(m, mdl))
+            processMethod(intentDecls, intents, m, mdl)
 
         // References classes. We can use static methods or try to initialize 
class instance using default constructor.
         for (ref <- refs; m <- getAllMethods(ref))
-            processMethod(intentDecls, intents, MethodOwner(m, ref))
+            processMethod(intentDecls, intents, m, ref)
 
         val unusedIntents = intentDecls.filter(i => !intents.exists(_._1.id == 
i.id))
 
         if unusedIntents.nonEmpty then
-            logger.warn(s"Intents are unused (have no callback): [mdlId=$id, 
origin=$origin, intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]")
+            logger.warn(s"Intents are unused (have no callback): 
[mdlId=$mdlId, origin=$origin, 
intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]")
 
-        if (intents.nonEmpty) {
+        if intents.nonEmpty then
             // Check the uniqueness of intent IDs.
             NCUtils.getDups(intents.map(_._1).toSeq.map(_.id)) match
-                case ids if ids.nonEmpty => E(s"Duplicate intent IDs 
[mdlId=$id, origin=$origin, ids=${ids.mkString(",")}]")
+                case ids if ids.nonEmpty => E(s"Duplicate intent IDs 
[mdlId=$mdlId, origin=$origin, ids=${ids.mkString(",")}]")
                 case _ => // No-op.
-        }
         else
-            logger.warn(s"Model has no intent: $id")
+            logger.warn(s"Model has no intent: $mdlId")
 
         intents.toSeq
 
     /**
       *
       * @param mtd
-      * @param paramCls
+      * @param argClasses
       * @param paramGenTypes
       * @param ctxFirstParam
       */
-    private def checkTypes(mtd: Method, paramCls: Seq[Class[_]], 
paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit =
-        require(paramCls.sizeIs == paramGenTypes.length)
+    private def checkTypes(mtd: Method, argClasses: Seq[Class[_]], 
paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit =
+        require(argClasses.sizeIs == paramGenTypes.length)
 
-        paramCls.zip(paramGenTypes).zipWithIndex.foreach { case ((pClass, 
pGenType), i) =>
+        var warned = false
+
+        argClasses.zip(paramGenTypes).zipWithIndex.foreach { case ((argClass, 
paramGenType), i) =>
             def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
 
             // Entity.
-            if pClass == CLS_ENTITY then () // No-op.
-            else if pClass.isArray then
-                val compType = pClass.getComponentType
+            if argClass == CLS_ENTITY then () // No-op.
+            else if argClass.isArray then
+                val compType = argClass.getComponentType
 
                 if compType != CLS_ENTITY then
-                    E(s"Unexpected array element type for @NCIntentTerm 
annotated argument [mdlId=$id, origin=$origin, type=${class2Str(compType)}, 
arg=${mkArg()}]")
+                    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(pClass) then
-                pGenType match
+            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=$id, origin=$origin, 
count=${compTypes.length}, arg=${mkArg()}]")
+                            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, Groovy.
+                            // 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=$id, origin=$origin, 
type=${class2Str(genClass)}, arg=${mkArg()}]")
+                                    E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, 
type=${class2Str(genClass)}, arg=${mkArg()}]")
 
                             // Kotlin.
                             case _: WildcardType =>
@@ -428,18 +462,20 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
                                 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=$id, origin=$origin, 
type=${wc2Str(wildcardType)}, arg=${mkArg()}]")
-                            case _ => E(s"Unexpected generic type for 
@NCIntentTerm annotated argument [mdlId=$id, origin=$origin, 
type=${compType.getTypeName}, arg=${mkArg()}]")
+                                    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()}]")
 
-//                    // TODO: scala 3 ParameterizedType doesn't here.
-//                    case  _ : Any if pGenType.getTypeName == 
"scala.collection.immutable.Seq" ||
-//                                     pGenType.getTypeName == 
"scala.collection.immutable.List" ||
-//                                     pGenType.getTypeName == "scala.Option"  
=> // No-op.
                     case _ =>
-                        E(s"Unexpected parameter type for @NCIntentTerm 
annotated argument [mdlId=$id, origin=$origin, type=${pGenType.getTypeName}, 
arg=${mkArg()}]")
+                        // Scala. For methods which added as
+                        if COMP_CLS.exists(_ == paramGenType) then
+                            if (!warned)
+                                warned = true // TODO: text
+                                logger.warn(s"Method arguments types cannot be 
detected and checked: ${method2Str(mtd)}")
+                        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=$id, origin=$origin, type=${class2Str(pClass)}, 
arg=${mkArg()}]")
+                E(s"Unexpected parameter type for @NCIntentTerm annotated 
argument [mdlId=$mdlId, origin=$origin, type=${class2Str(argClass)}, 
arg=${mkArg()}]")
         }
 
     /**
@@ -457,7 +493,7 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
             def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
 
             val p1 = "its @NCIntentTerm annotated argument"
-            val p2 = s"[mdlId=$id, origin=$origin, arg=${mkArg()}]"
+            val p2 = s"[mdlId=$mdlId, origin=$origin, arg=${mkArg()}]"
 
             // Argument is single entity but defined as not single entity.
             if cls == CLS_ENTITY && (min != 1 || max != 1) then
@@ -475,60 +511,61 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
 
     /**
       *
-      * @param mo
+      * @param intentDecls
+      * @param intents
+      * @param method
+      * @param obj
       */
-    private def processMethod(intentDecls: mutable.Buffer[NCIDLIntent], 
intents: mutable.Buffer[NCIntentData], mo: MethodOwner): Unit =
-        val m = mo.method
-        val mtdStr = method2Str(m)
-        lazy val samples = scanSamples(mo.method)
+    private def processMethod(intentDecls: mutable.Buffer[NCIDLIntent], 
intents: mutable.Buffer[NCIntentData], method: Method, obj: Object): Unit =
+        val mtdStr = method2Str(method)
+        lazy val samples = scanSamples(method)
 
         def bindIntent(intent: NCIDLIntent, cb: NCCallback): Unit =
             if intents.exists(i => i._1.id == intent.id && i._2.id != cb.id) 
then
-                E(s"The intent cannot be bound to more than one callback 
[mdlId=$id, origin=$origin, class=${mo.className}, intentId=${intent.id}]")
+                E(s"The intent cannot be bound to more than one callback 
[mdlId=$mdlId, origin=$origin, class=${getClassName(method.getDeclaringClass)}, 
intentId=${intent.id}]")
             else
                 intentDecls += intent
-                intents += NCIntentData(intent, prepareCallback(mo, intent), 
samples.getOrElse(intent.id, Seq.empty))
+                intents += NCIntentData(intent, cb, 
samples.getOrElse(intent.id, Seq.empty))
 
         def existsForOtherMethod(id: String): Boolean =
             intents.find(_.intent.id == id) match
-                case Some(i) => i.callback.method != m
+                case Some(i) => i.callback.method != method
                 case None => false
 
         // 1. Process inline intent declarations by @NCIntent annotation.
-        val annsIntents = m.getAnnotationsByType(CLS_INTENT)
+        val annsIntents = method.getAnnotationsByType(CLS_INTENT)
         checkSingle(annsIntents, (a:NCIntent) => a.value, mtdStr)
 
         for (ann <- annsIntents; intent <- NCIDLCompiler.compile(ann.value, 
cfg, mtdStr))
             if intentDecls.exists(_.id == intent.id && 
existsForOtherMethod(intent.id)) then
-                E(s"Duplicate intent ID [mdlId=$id, origin=$origin, 
callback=$mtdStr, id=${intent.id}]")
+                E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin, 
callback=$mtdStr, id=${intent.id}]")
             else
-                bindIntent(intent, prepareCallback(mo, intent))
+                bindIntent(intent, prepareCallback(method, obj, intent))
 
         // 2. Process intent references from @NCIntentRef annotation.
-        val annRefs = m.getAnnotationsByType(CLS_INTENT_REF)
+        val annRefs = method.getAnnotationsByType(CLS_INTENT_REF)
         checkSingle(annRefs, (a:NCIntentRef) => a.value, mtdStr)
 
         for (ann <- annRefs)
             val refId = ann.value.trim
 
             intentDecls.find(_.id == refId) match
-                case Some(intent) => bindIntent(intent, prepareCallback(mo, 
intent))
-                case None => E(s"@NCIntentRef(\"$refId\") references unknown 
intent ID [mdlId=$id, origin=$origin, refId=$refId, callback=$mtdStr]")
+                case Some(intent) => bindIntent(intent, 
prepareCallback(method, obj, intent))
+                case None => E(s"@NCIntentRef(\"$refId\") references unknown 
intent ID [mdlId=$mdlId, origin=$origin, refId=$refId, callback=$mtdStr]")
 
     /**
       *
-      * @param mo
+      * @param method
+      * @param obj
       * @param intent
       * @return
       */
-    private def prepareCallback(mo: MethodOwner, intent: NCIDLIntent): 
NCCallback =
-        val mtd = mo.method
-
+    private def prepareCallback(method: Method, obj: Object, intent: 
NCIDLIntent): NCCallback =
         // Checks method result type.
-        if mtd.getReturnType != CLS_QRY_RES then
-            E(s"Unexpected result type for @NCIntent annotated method 
[mdlId=$id, intentId=${intent.id}, type=${class2Str(mtd.getReturnType)}, 
callback=${method2Str(mtd)}]")
+        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)}]")
 
-        val allParamTypes = mtd.getParameterTypes.toSeq
+        val allParamTypes = method.getParameterTypes.toSeq
         val ctxFirstParam = allParamTypes.nonEmpty && allParamTypes.head == 
CLS_INTENT_MATCH
 
         def getSeq[T](data: Seq[T]): Seq[T] =
@@ -536,18 +573,18 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
             else if ctxFirstParam then data.drop(1)
             else data
 
-        val allAnns = mtd.getParameterAnnotations
+        val allAnns = method.getParameterAnnotations
         val tokParamAnns = getSeq(allAnns.toIndexedSeq).filter(_ != null)
         val tokParamTypes = getSeq(allParamTypes)
 
         // Checks entities parameters annotations count.
         if tokParamAnns.sizeIs != tokParamTypes.length then
-            E(s"Unexpected annotations count for @NCIntent annotated method 
[mdlId=$id, intentId=${intent.id}, count=${tokParamAnns.size}, 
callback=${method2Str(mtd)}]")
+            E(s"Unexpected annotations count for @NCIntent annotated method 
[mdlId=$mdlId, intentId=${intent.id}, count=${tokParamAnns.size}, 
callback=${method2Str(method)}]")
 
         // Gets terms IDs.
         val termIds = tokParamAnns.toList.zipWithIndex.map {
             case (annArr, idx) =>
-                def mkArg(): String = arg2Str(mtd, idx, ctxFirstParam)
+                def mkArg(): String = arg2Str(method, idx, ctxFirstParam)
 
                 val termAnns = annArr.filter(_.isInstanceOf[NCIntentTerm])
 
@@ -556,15 +593,15 @@ class NCAnnotationsScanner(mdl: NCModel) 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=$id, 
intentId=${intent.id}, arg=${mkArg()}]")
+                            E(s"Missing @NCIntentTerm annotation or wrong type 
of the 1st parameter (must be 'NCIntentMatch') for [mdlId=$mdlId, 
intentId=${intent.id}, arg=${mkArg()}]")
                         else
-                            E(s"Missing @NCIntentTerm annotation for 
[mdlId=$id, intentId=${intent.id}, arg=${mkArg()}]")
+                            E(s"Missing @NCIntentTerm annotation for 
[mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]")
 
-                    case _ => E(s"Too many @NCIntentTerm annotations for 
[mdlId=$id, 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=$id, 
intentId=${intent.id}, dups=${NCUtils.getDups(termIds).mkString(", ")}, 
callback=${method2Str(mtd)}]")
+            E(s"Duplicate term IDs in @NCIntentTerm annotations [mdlId=$mdlId, 
intentId=${intent.id}, dups=${NCUtils.getDups(termIds).mkString(", ")}, 
callback=${method2Str(method)}]")
 
         val terms = intent.terms
 
@@ -575,27 +612,25 @@ class NCAnnotationsScanner(mdl: NCModel) extends 
LazyLogging:
 
         if invalidIds.nonEmpty then
             // Report only the first one for simplicity & clarity.
-            E(s"Unknown term ID in @NCIntentTerm annotation [mdlId=$id, 
intentId=${intent.id}, termId=${invalidIds.head}, callback=${method2Str(mtd)}]")
-
-        val paramGenTypes = getSeq(mtd.getGenericParameterTypes.toIndexedSeq)
+            E(s"Unknown term ID in @NCIntentTerm annotation [mdlId=$mdlId, 
intentId=${intent.id}, termId=${invalidIds.head}, 
callback=${method2Str(method)}]")
 
-        require(tokParamTypes.sizeIs == paramGenTypes.length)
+        val paramGenTypes = 
getSeq(method.getGenericParameterTypes.toIndexedSeq)
 
         // Checks parameters.
-        checkTypes(mtd, tokParamTypes, paramGenTypes, ctxFirstParam)
+        checkTypes(method, tokParamTypes, paramGenTypes, ctxFirstParam)
 
         // Checks limits.
         val allLimits = terms.map(t => t.id.orNull -> (t.min, t.max)).toMap
 
-        checkMinMax(mtd, tokParamTypes, termIds.map(allLimits), ctxFirstParam)
+        checkMinMax(method, tokParamTypes, termIds.map(allLimits), 
ctxFirstParam)
 
-        NCCallback(mtd,
+        NCCallback(method,
             (ctx: NCIntentMatch) =>
                 val args = mutable.Buffer.empty[AnyRef]
                 if ctxFirstParam then args += ctx
-                args ++= prepareParams(id, mtd, tokParamTypes, 
termIds.map(ctx.getTermEntities), ctxFirstParam)
+                args ++= prepareParams(mdlId, method, tokParamTypes, 
termIds.map(ctx.getTermEntities), ctxFirstParam)
 
-                invoke(id, mo, args.toArray)
+                invoke(mdlId, method, obj, args.toArray)
         )
 
     /**
diff --git a/nlpcraft/src/test/resources/scan/idl.idl 
b/nlpcraft/src/test/resources/scan/idl.idl
index 674cb86..24fa7ff 100644
--- a/nlpcraft/src/test/resources/scan/idl.idl
+++ b/nlpcraft/src/test/resources/scan/idl.idl
@@ -15,4 +15,4 @@
  * limitations under the License.
  */
 
-intent=intent2 term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] 
term(opt)~{# == 'id3'}?
\ No newline at end of file
+intent=impIntId term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] 
term(opt)~{# == 'id3'}?
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelScala.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelScala.scala
deleted file mode 100644
index a01f6c2..0000000
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelScala.scala
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.apache.nlpcraft.internal.impl
-
-import org.apache.nlpcraft.*
-import org.apache.nlpcraft.nlp.util.opennlp.*
-import org.junit.jupiter.api.Test
-
-object NCTestModelScala:
-    @NCIntentImport(Array("scan/idl.idl"))
-    object NCTestModelScalaObj extends NCModel:
-        override def getConfig: NCModelConfig = CFG
-        override def getPipeline: NCModelPipeline = EN_PIPELINE
-
-        @NCIntent("intent=intent1 term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
-        @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
-        def intent1(
-            @NCIntentTerm("single") single: NCEntity,
-            @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq.
-            @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = new NCResult()
-
-        @NCIntentRef("intent2")
-        @NCIntentSampleRef("scan/samples.txt")
-        def intent2(
-            @NCIntentTerm("single") single: NCEntity,
-            @NCIntentTerm("list") list: List[NCEntity], // scala List.
-            @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = new NCResult()
-
-    @NCIntentImport(Array("scan/idl.idl"))
-    class NCTestModelScalaClass extends NCModel:
-        override def getConfig: NCModelConfig = CFG
-        override def getPipeline: NCModelPipeline = EN_PIPELINE
-
-        @NCIntent("intent=intent1 term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
-        @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
-        def intent1(
-            @NCIntentTerm("single") single: NCEntity,
-            @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq.
-            @NCIntentTerm("opt") opt: Option[NCEntity]
-        ) = new NCResult()
-
-        @NCIntentRef("intent2")
-        @NCIntentSampleRef("scan/samples.txt")
-        def intent2(
-            @NCIntentTerm("single") single: NCEntity,
-            @NCIntentTerm("list") list: List[NCEntity], // scala List.
-            @NCIntentTerm("opt") opt: Option[NCEntity]
-        ) = new NCResult()
-
-    def mkModel: NCModel =
-        new NCModelAdapter(CFG, EN_PIPELINE):
-            @NCIntentImport(Array("scan/idl.idl"))
-            @NCIntent("intent=intent1 term(single)~{# == 'id1'} term(list)~{# 
== 'id2'}[0,10] term(opt)~{# == 'id3'}?")
-            @NCIntentSample(Array("What are the least performing categories 
for the last quarter?"))
-            def intent1(
-                @NCIntentTerm("single") single: NCEntity,
-                @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq.
-                @NCIntentTerm("opt") opt: Option[NCEntity]
-            ): NCResult = new NCResult()
-
-            @NCIntentRef("intent2")
-            @NCIntentSampleRef("scan/samples.txt")
-            def intent2(
-                @NCIntentTerm("single") single: NCEntity,
-                @NCIntentTerm("list") list: List[NCEntity], // scala List.
-                @NCIntentTerm("opt") opt: Option[NCEntity]
-            ): NCResult = new NCResult()
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationInvalidArgsSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationInvalidArgsSpec.scala
new file mode 100644
index 0000000..1314dd0
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationInvalidArgsSpec.scala
@@ -0,0 +1,159 @@
+/*
+ * 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.NCAnnotationsScanner
+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 methods parameters types usage.
+  * Note that for some kind of models (it depends on creation type) we can't 
check methods arguments during scan.
+  */
+class NCAnnotationInvalidArgsSpec:
+    class DefinedClassModelValid extends NCModel:
+        override def getConfig: NCModelConfig = CFG
+        override def getPipeline: NCModelPipeline = EN_PIPELINE
+
+        // Valid parameters.
+        @NCIntent("intent=validList term(list)~{# == 'x'}[0,10]")
+        def validList(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
processListEntity(list)
+
+        @NCIntent("intent=validOpt term(opt)~{# == 'x'}?")
+        def validOpt(@NCIntentTerm("opt") opt: Option[NCEntity]): NCResult = 
processOptEntity(opt)
+
+    class DefinedClassModelInvalidLst extends NCModel:
+        override def getConfig: NCModelConfig = CFG
+        override def getPipeline: NCModelPipeline = EN_PIPELINE
+
+        // Invalid parameters.
+        @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]")
+        def invalidList(@NCIntentTerm("list") list: List[Int]): NCResult = 
processListInt(list)
+
+    class DefinedClassModelInvalidOpt extends NCModel:
+        override def getConfig: NCModelConfig = CFG
+        override def getPipeline: NCModelPipeline = EN_PIPELINE
+
+        // Invalid parameters.
+        @NCIntent("intent=invalidOpt term(opt)~{# == 'x'}?")
+        def invalidOpt(@NCIntentTerm("opt") opt: Option[Int]): NCResult = 
processOptInt(opt)
+
+
+    private val CHECKED_MDL_VALID: NCModel = new DefinedClassModelValid
+    private val CHECKED_MDL_INVALID_LST: NCModel = new 
DefinedClassModelInvalidLst
+    private val CHECKED_MDL_INVALID_OPT: NCModel = new 
DefinedClassModelInvalidOpt
+    private val UNCHECKED_MDL_MIX: NCModel =
+        new NCModel:
+            override def getConfig: NCModelConfig = CFG
+            override def getPipeline: NCModelPipeline = EN_PIPELINE
+
+            // Valid parameters.
+            @NCIntent("intent=validList term(list)~{# == 'x'}[0,10]")
+            def validList(@NCIntentTerm("list") list: List[NCEntity]): 
NCResult = processListEntity(list)
+
+            @NCIntent("intent=validOpt term(opt)~{# == 'x'}?")
+            def validOpt(@NCIntentTerm("opt") opt: Option[NCEntity]): NCResult 
= processOptEntity(opt)
+
+            // Invalid parameters.
+            @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]")
+            def invalidList(@NCIntentTerm("list") list: List[Int]): NCResult = 
processListInt(list)
+
+            @NCIntent("intent=invalidOpt term(opt)~{# == 'x'}?")
+            def invalidOpt(@NCIntentTerm("opt") opt: Option[Int]): NCResult = 
processOptInt(opt)
+
+    private val INTENT_MATCH =
+        val ent = NCTestEntity("id", "reqId", tokens = NCTestToken())
+
+        new NCIntentMatch:
+            override def getIntentId: String = "impIntId"
+            override def getIntentEntities: util.List[util.List[NCEntity]] = 
col(col(ent))
+            override def getTermEntities(idx: Int): util.List[NCEntity] = 
col(ent)
+            override def getTermEntities(termId: String): util.List[NCEntity] 
= col(ent)
+            override def getVariant: NCVariant = new NCVariant:
+                override def getEntities: util.List[NCEntity] = col(ent)
+
+    private def mkResult0(obj: Any): NCResult =
+        println(s"Result body: $obj, class=${obj.getClass}")
+        val res = new NCResult()
+        res.setBody(obj)
+        res
+
+    private def processOptInt(opt: Option[Int]): NCResult =
+        // Access and cast.
+        val body: Int = opt.get
+        mkResult0(body)
+
+    private def processOptEntity(opt: Option[NCEntity]): NCResult =
+        // Access and cast.
+        val body: NCEntity = opt.get
+        mkResult0(body)
+
+    private def processListInt(list: List[Int]): NCResult =
+        // Access and cast.
+        val bodyHead: Int = list.head
+        mkResult0(list)
+
+    private def processListEntity(list: List[NCEntity]): NCResult =
+        // Access and cast.
+        val bodyHead: NCEntity = list.head
+        mkResult0(list)
+
+    private def col[T](t: T): util.List[T] = 
java.util.Collections.singletonList(t)
+
+    private def testOk(mdl: NCModel, intentId: String): Unit =
+        val cb = new NCAnnotationsScanner(mdl).scan().find(_.intent.id == 
intentId).get.callback
+
+        println(s"Test finished [modelClass=${mdl.getClass}, intent=$intentId, 
result=${cb.cbFun.apply(INTENT_MATCH)}")
+
+    private def testRuntimeClassCast(mdl: NCModel, intentId: String): Unit =
+        val cb = new NCAnnotationsScanner(mdl).scan().find(_.intent.id == 
intentId).get.callback
+
+        try
+            cb.cbFun.apply(INTENT_MATCH)
+
+            require(false)
+        catch
+            case e: NCException =>
+                if e.getCause != null && 
e.getCause.isInstanceOf[ClassCastException] then
+                    println(s"Expected error: $e")
+                    e.printStackTrace(System.out)
+                else throw e
+
+    private def testScanError(mdl: NCModel, intentId: String): Unit =
+        try
+            new NCAnnotationsScanner(mdl)
+        catch
+            case e: NCException =>
+                println(s"Expected error: $e")
+                e.printStackTrace(System.out)
+
+    @Test
+    def test(): Unit =
+        testOk(CHECKED_MDL_VALID, "validList")
+        testOk(CHECKED_MDL_VALID, "validOpt")
+        testScanError(CHECKED_MDL_INVALID_LST, "invalidList")
+        testScanError(CHECKED_MDL_INVALID_OPT, "invalidOpt")
+
+        testOk(UNCHECKED_MDL_MIX, "validList")
+        testOk(UNCHECKED_MDL_MIX, "validOpt")
+        testRuntimeClassCast(UNCHECKED_MDL_MIX, "invalidList")
+        testRuntimeClassCast(UNCHECKED_MDL_MIX, "invalidOpt")
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationNestedSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationNestedSpec.scala
new file mode 100644
index 0000000..003dbb7
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationNestedSpec.scala
@@ -0,0 +1,58 @@
+/*
+ * 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.NCAnnotationsScanner
+import org.apache.nlpcraft.nlp.util.opennlp.*
+import org.junit.jupiter.api.Test
+
+/**
+  * It tests imports and nested objects usage.
+  */
+class NCAnnotationNestedSpec:
+    private val mdl: NCModel = new NCModel :
+        override def getConfig: NCModelConfig = CFG
+        override def getPipeline: NCModelPipeline = EN_PIPELINE
+
+        @NCIntentObject
+        val nested: Object = new Object() {
+            @NCIntentObject
+            val nested2: Object = new Object() {
+                @NCIntentImport(Array("scan/idl.idl"))
+                @NCIntent("intent=intent3 term(x)~{true}")
+                def intent1(@NCIntentTerm("x") x: NCEntity) = new NCResult()
+            }
+
+            @NCIntent("intent=intent2 term(x)~{true}")
+            def intent1(@NCIntentTerm("x") x: NCEntity) = new NCResult()
+        }
+
+        @NCIntent("intent=intent1 term(x)~{true}")
+        def intent1(@NCIntentTerm("x") x: NCEntity) = new NCResult()
+
+        // Imported in `nested2` (scan/idl.idl)
+        @NCIntentRef("impIntId")
+        def intent4(
+            @NCIntentTerm("single") single: NCEntity,
+            @NCIntentTerm("list") list: List[NCEntity],
+            @NCIntentTerm("opt") opt: Option[NCEntity]
+        ): NCResult = new NCResult()
+
+    @Test
+    def test(): Unit = require(new NCAnnotationsScanner(mdl).scan().sizeIs == 
4)
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScannerSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationSpec.scala
similarity index 82%
rename from 
nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScannerSpec.scala
rename to 
nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationSpec.scala
index 0e75a7c..2e8f541 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScannerSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationSpec.scala
@@ -15,16 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.nlpcraft.internal.impl
+package org.apache.nlpcraft.internal.impl.scan
 
 import org.apache.nlpcraft.NCModel
+import org.apache.nlpcraft.internal.impl.*
 import org.junit.jupiter.api.Test
 
 /**
-  *
+  * It tests various ways created models scanning.
   */
-class NCAnnotationsScannerSpec:
-    // Different ways for preparing model instances.
+class NCAnnotationSpec:
+    // Different ways preparing model instances.
     private val mdls = Seq(
         NCTestModelJava.mkModel,
         NCTestModelScala.mkModel,
@@ -35,6 +36,6 @@ class NCAnnotationsScannerSpec:
     @Test
     def test(): Unit =
         for (mdl <- mdls)
+            println(s"Model=${mdl.getClass}")
             val data = new NCAnnotationsScanner(mdl).scan()
-
             println(s"Data=$data")
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelJava.java
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java
similarity index 88%
rename from 
nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelJava.java
rename to 
nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java
index bc40b2c..c407748 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelJava.java
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.nlpcraft.internal.impl;
+package org.apache.nlpcraft.internal.impl.scan;
 
 import org.apache.nlpcraft.NCIntent;
 import org.apache.nlpcraft.NCIntentImport;
@@ -36,15 +36,19 @@ import java.util.Optional;
  *
  */
 public class NCTestModelJava {
+    /**
+     *
+     * @return
+     */
     public static NCModel mkModel() {
         return
             new NCModelAdapter(NCTestConfigJava.CFG, 
NCTestConfigJava.EN_PIPELINE) {
                 @NCIntentImport({"scan/idl.idl"})
                 @NCIntent(
-                    "intent=intent1 term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?"
+                    "intent=locInt term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?"
                 )
                 @NCIntentSample("What are the least performing categories for 
the last quarter?")
-                NCResult intent1(
+                NCResult intent(
                     @NCIntentTerm("single") NCEntity single,
                     @NCIntentTerm("list") List<NCEntity> list,
                     @NCIntentTerm("opt") Optional<NCEntity> opt
@@ -52,9 +56,9 @@ public class NCTestModelJava {
                     return new NCResult();
                 }
 
-                @NCIntentRef("intent2")
+                @NCIntentRef("impIntId")
                 @NCIntentSampleRef("scan/samples.txt")
-                NCResult intent2(
+                NCResult intentImport(
                     @NCIntentTerm("single") NCEntity single,
                     @NCIntentTerm("list") List<NCEntity> list,
                     @NCIntentTerm("opt") Optional<NCEntity> opt
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
new file mode 100644
index 0000000..fe691b1
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
@@ -0,0 +1,88 @@
+/*
+ * 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.nlp.util.opennlp.*
+
+/**
+  *
+  */
+object NCTestModelScala:
+    @NCIntentImport(Array("scan/idl.idl"))
+    object NCTestModelScalaObj extends NCModel :
+        override def getConfig: NCModelConfig = CFG
+        override def getPipeline: NCModelPipeline = EN_PIPELINE
+
+        @NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
+        @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
+        def intent(
+            @NCIntentTerm("single") single: NCEntity,
+            @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq.
+            @NCIntentTerm("opt") opt: Option[NCEntity]
+        ): NCResult = new NCResult()
+
+        @NCIntentRef("impIntId")
+        @NCIntentSampleRef("scan/samples.txt")
+        def intentImport(
+            @NCIntentTerm("single") single: NCEntity,
+            @NCIntentTerm("list") list: List[NCEntity], // scala List.
+            @NCIntentTerm("opt") opt: Option[NCEntity]
+        ): NCResult = new NCResult()
+
+    @NCIntentImport(Array("scan/idl.idl")) class NCTestModelScalaClass extends 
NCModel :
+        override def getConfig: NCModelConfig = CFG
+        override def getPipeline: NCModelPipeline = EN_PIPELINE
+
+        @NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
+        @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
+        def intent(
+            @NCIntentTerm("single") single: NCEntity,
+            @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq.
+            @NCIntentTerm("opt") opt: Option[NCEntity]
+        ) = new NCResult()
+
+        @NCIntentRef("impIntId")
+        @NCIntentSampleRef("scan/samples.txt")
+        def intentImport(
+            @NCIntentTerm("single") single: NCEntity,
+            @NCIntentTerm("list") list: List[NCEntity], // scala List.
+            @NCIntentTerm("opt") opt: Option[NCEntity]
+        ) = new NCResult()
+
+    /**
+      *
+      * @return
+      */
+    def mkModel: NCModel = new NCModelAdapter(CFG, EN_PIPELINE) :
+        @NCIntentImport(Array("scan/idl.idl"))
+        @NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
+        @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
+        def intent(
+            @NCIntentTerm("single") single: NCEntity,
+            @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq.
+            @NCIntentTerm("opt") opt: Option[NCEntity]
+        ): NCResult = new NCResult()
+
+        @NCIntentRef("impIntId")
+        @NCIntentSampleRef("scan/samples.txt")
+        def intentImport(
+            @NCIntentTerm("single") single: NCEntity,
+            @NCIntentTerm("list") list: List[NCEntity], // scala List.
+            @NCIntentTerm("opt") opt: Option[NCEntity]
+        ): NCResult = new NCResult()
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala
index a1c1045..2df2308 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala
@@ -21,7 +21,7 @@ import org.apache.nlpcraft.*
 import org.apache.nlpcraft.internal.intent.*
 import org.apache.nlpcraft.internal.intent.compiler.*
 import org.apache.nlpcraft.internal.intent.compiler.functions.*
-import org.apache.nlpcraft.nlp.util.{NCTestRequest, NCTestToken}
+import org.apache.nlpcraft.nlp.util.*
 import org.apache.nlpcraft.nlp.util.opennlp.*
 import org.junit.jupiter.api.BeforeEach
 
@@ -140,7 +140,7 @@ private[functions] object NCIDLFunctions:
     /**
       *
       * @param id
-      * @param srvReqId
+      * @param reqId
       * @param value
       * @param groups
       * @param meta
@@ -149,21 +149,15 @@ private[functions] object NCIDLFunctions:
       */
     def mkEntity(
         id: String = UUID.randomUUID().toString,
-        srvReqId: String = UUID.randomUUID().toString,
-        value: String = null,
+        reqId: String = UUID.randomUUID().toString,
+        value: String = null, // TODO: add tests for usage.
         groups: Set[String] = null,
         meta: Map[String, AnyRef] = Map.empty[String, AnyRef],
         tokens: NCTestToken*
     ): NCEntity =
         require(tokens.nonEmpty)
 
-        new NCPropertyMapAdapter with NCEntity():
-            meta.foreach { (k, v) => put(k, v) }
-
-            override def getTokens: java.util.List[NCToken] = tokens.asJava
-            override def getRequestId: String = srvReqId
-            override def getGroups: util.Set[String] = if groups != null then 
groups.asJava else util.Collections.singleton(id)
-            override def getId: String = id
+        NCTestEntity(id, reqId, groups, meta, tokens*)
 
 import org.apache.nlpcraft.internal.intent.compiler.functions.NCIDLFunctions.*
 
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestEntity.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestEntity.scala
new file mode 100644
index 0000000..0640a80
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestEntity.scala
@@ -0,0 +1,47 @@
+/*
+ * 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.nlp.util
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.nlp.util.NCTestPipeline.*
+import scala.jdk.CollectionConverters.*
+import java.util.Set as JSet
+import java.util.List as JList
+
+/**
+  * Entity test implementation.
+  *
+  * @param id
+  * @param reqId
+  * @param groups
+  * @param meta
+  * @param tokens
+  */
+case class NCTestEntity(
+    id: String,
+    reqId: String = null,
+    groups: Set[String] = null,
+    meta: Map[String, AnyRef] = null,
+    tokens: NCTestToken*
+) extends NCPropertyMapAdapter with NCEntity:
+    if meta != null then meta.foreach { (k, v) => put(k, v) }
+
+    override def getTokens: JList[NCToken] = tokens.asJava
+    override def getRequestId: String = reqId
+    override def getGroups: JSet[String] = (if groups != null then groups else 
Set(id)).asJava
+    override def getId: String = id
\ No newline at end of file

Reply via email to