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