Annotations added: NCModelAddPackage, NCModelAddClasses.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala
index d222e5e..da194cc 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala
@@ -137,7 +137,6 @@
def kb: Long = KB
}
-
/**
* Pimps integers with time units.
*
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
index 1fbde7e..d8c1900 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
@@ -84,9 +84,7 @@
private final val DISABLE_GA_PROP = "NLPCRAFT_DISABLE_GA"
private lazy val ANSI_FG_8BIT_COLORS = for (i <- 16 to 255) yield ansi256Fg(i)
-
private lazy val ANSI_BG_8BIT_COLORS = for (i <- 16 to 255) yield ansi256Bg(i)
-
private lazy val ANSI_FG_4BIT_COLORS = Seq(
ansiRedFg,
ansiGreenFg,
@@ -300,13 +298,14 @@
*
* @param f Closure to convert.
*/
- implicit def toRun(f: => Unit): Runnable = () => try {
- f
- }
- catch {
- case _: InterruptedException => Thread.currentThread().interrupt()
- case e: Throwable => prettyError(logger, "Unhandled exception caught:", e)
- }
+ implicit def toRun(f: => Unit): Runnable = () =>
+ try {
+ f
+ }
+ catch {
+ case _: InterruptedException => Thread.currentThread().interrupt()
+ case e: Throwable => prettyError(logger, "Unhandled exception caught:", e)
+ }
/**
* Destroys given process (using proper waiting algorithm).
@@ -874,9 +873,7 @@
*
* @param res Resource.
*/
- @throws[NCE]
- def hasResource(res: String): Boolean =
- getClass.getClassLoader.getResourceAsStream(res) != null
+ def hasResource(res: String): Boolean = getClass.getClassLoader.getResourceAsStream(res) != null
/**
* Serializes data.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntent.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntent.java
index 877f7d7..484a1d3 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntent.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntent.java
@@ -54,6 +54,8 @@
* @see NCIntentTerm
* @see NCIntentSample
* @see NCIntentSampleRef
+ * @see NCModelAddClasses
+ * @see NCModelAddPackage
* @see NCIntentSkip
* @see NCIntentMatch
* @see NCModel#onMatchedIntent(NCIntentMatch)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentMatch.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentMatch.java
index 2f1d382..8268d75 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentMatch.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentMatch.java
@@ -32,6 +32,8 @@
* @see NCIntent
* @see NCIntentTerm
* @see NCIntentSkip
+ * @see NCModelAddClasses
+ * @see NCModelAddPackage
* @see NCIntentSample
* @see NCIntentSampleRef
* @see NCIntentRef
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentRef.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentRef.java
index 263f09f..3348ffe 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentRef.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentRef.java
@@ -46,6 +46,8 @@
* @see NCIntentTerm
* @see NCIntentSample
* @see NCIntentSampleRef
+ * @see NCModelAddClasses
+ * @see NCModelAddPackage
* @see NCIntentSkip
* @see NCIntentMatch
* @see NCModel#onMatchedIntent(NCIntentMatch)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSample.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSample.java
index e8d18c7..6d57d0d 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSample.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSample.java
@@ -76,6 +76,8 @@
* @see NCIntent
* @see NCIntentRef
* @see NCIntentTerm
+ * @see NCModelAddClasses
+ * @see NCModelAddPackage
* @see NCIntentSkip
* @see NCIntentMatch
* @see NCModel#onMatchedIntent(NCIntentMatch)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSampleRef.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSampleRef.java
index 2596611..a455b8a 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSampleRef.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentSampleRef.java
@@ -75,6 +75,8 @@
* @see NCIntent
* @see NCIntentRef
* @see NCIntentTerm
+ * @see NCModelAddClasses
+ * @see NCModelAddPackage
* @see NCIntentSkip
* @see NCIntentMatch
* @see NCModel#onMatchedIntent(NCIntentMatch)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentTerm.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentTerm.java
index e864eb5..13832a0 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentTerm.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCIntentTerm.java
@@ -58,6 +58,8 @@
* @see NCIntentRef
* @see NCIntentSample
* @see NCIntentSampleRef
+ * @see NCModelAddClasses
+ * @see NCModelAddPackage
* @see NCIntentSkip
* @see NCIntentMatch
* @see NCModel#onMatchedIntent(NCIntentMatch)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelAddClasses.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelAddClasses.java
new file mode 100644
index 0000000..d8241d4
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelAddClasses.java
@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ * http://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.model;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to add one or more classes that contain intent callbacks. This annotation should be applied to the main
+ * model class. When found the internal intent detection algorithm will scan these additional classes searching
+ * for intent callbacks.
+ * <p>
+ * Additionally with {@link NCModelAddPackage} annotation, these two annotations allowing to have model implementation,
+ * i.e. intent callbacks, in external classes not linked through sub-type relationship to the main model class. This
+ * approach provides greater modularity, isolated testability and overall coding efficiencies for the larger models
+ * <p>
+ * Read full documentation in <a target=_ href="https://nlpcraft.apache.org/intent-matching.html#binding">Intent Matching</a> section and review
+ * <a target=_ href="https://github.com/apache/incubator-nlpcraft/tree/master/nlpcraft-examples">examples</a>.
+ *
+ * @see NCModelAddPackage
+ * @see NCIntentRef
+ * @see NCIntentTerm
+ * @see NCIntentSample
+ * @see NCIntentSampleRef
+ * @see NCIntentSkip
+ * @see NCIntentMatch
+ */
+@Retention(value=RUNTIME)
+@Target(value=TYPE)
+public @interface NCModelAddClasses {
+ /**
+ * Array of class instances to additionally scan for intent callbacks.
+ *
+ * @return Array of class instances to additionally scan for intent callbacks.
+ */
+ Class<?>[] value();
+}
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelAddPackage.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelAddPackage.java
new file mode 100644
index 0000000..41ca9dc
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelAddPackage.java
@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ * http://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.model;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to add one or more JVM packages that contain classes with intent callbacks. This annotation should be
+ * applied to the main model class. When found the internal intent detection algorithm will recursively scan these
+ * additional packages and their classes searching for intent callbacks.
+ * <p>
+ * Additionally with {@link NCModelAddClasses} annotation, these two annotations allowing to have model implementation,
+ * i.e. intent callbacks, in external classes not linked through sub-type relationship to the main model class. This
+ * approach provides greater modularity, isolated testability and overall coding efficiencies for the larger models
+ * <p>
+ * Read full documentation in <a target=_ href="https://nlpcraft.apache.org/intent-matching.html#binding">Intent Matching</a> section and review
+ * <a target=_ href="https://github.com/apache/incubator-nlpcraft/tree/master/nlpcraft-examples">examples</a>.
+ *
+ * @see NCModelAddClasses
+ * @see NCIntentRef
+ * @see NCIntentTerm
+ * @see NCIntentSample
+ * @see NCIntentSampleRef
+ * @see NCIntentSkip
+ * @see NCIntentMatch
+ */
+@Retention(value=RUNTIME)
+@Target(value=TYPE)
+public @interface NCModelAddPackage {
+ /**
+ * Array of JVM package names to recursively scan for intent callbacks.
+ *
+ * @return Array of JVM package names to recursively scan for intent callbacks.
+ */
+ String[] value();
+}
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
index f6ee6eb..e27eee6 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
@@ -17,6 +17,8 @@
package org.apache.nlpcraft.probe.mgrs.deploy
+import com.google.common.reflect.ClassPath
+
import java.io._
import java.lang.reflect.{InvocationTargetException, Method, Modifier, ParameterizedType, Type, WildcardType}
import java.util
@@ -39,6 +41,7 @@
import org.apache.nlpcraft.probe.mgrs.NCProbeSynonymChunkKind.{IDL, REGEX, TEXT}
import org.apache.nlpcraft.probe.mgrs.{NCProbeModel, NCProbeModelCallback, NCProbeSynonym, NCProbeSynonymChunk, NCProbeSynonymsWrapper}
+import java.lang.annotation.Annotation
import scala.util.Using
import scala.compat.java8.OptionConverters._
import scala.collection.mutable
@@ -58,6 +61,8 @@
private final val CLS_SLV_CTX = classOf[NCIntentMatch]
private final val CLS_SAMPLE = classOf[NCIntentSample]
private final val CLS_SAMPLE_REF = classOf[NCIntentSampleRef]
+ private final val CLS_MDL_CLS_REF = classOf[NCModelAddClasses]
+ private final val CLS_MDL_PKGS_REF = classOf[NCModelAddPackage]
// Java and scala lists.
private final val CLS_SCALA_SEQ = classOf[Seq[_]]
@@ -76,12 +81,15 @@
CLS_JAVA_OPT
)
- case class Callback(id: String, clsName: String, funName: String, cbFun: Function[NCIntentMatch, NCResult])
+ case class Callback(method: Method, cbFun: Function[NCIntentMatch, NCResult]) {
+ val id: String = method.toString
+ val clsName: String = method.getDeclaringClass.getName
+ val funName: String = method.getName
+ }
type Intent = (NCIdlIntent, Callback)
type Sample = (String/* Intent ID */, Seq[Seq[String]] /* List of list of input samples for that intent. */)
private final val SEPARATORS = Seq('?', ',', '.', '-', '!')
-
private final val SUSP_SYNS_CHARS = Seq("?", "*", "+")
@volatile private var data: mutable.ArrayBuffer[NCProbeModel] = _
@@ -104,6 +112,25 @@
*/
case class SynonymHolder(elmId: String, syn: NCProbeSynonym)
+ case class MethodOwner(method: Method, objClassName: String, obj: Any) {
+ require(method != null)
+ require(objClassName != null ^ obj != null)
+
+ private var lazyObj: Any = obj
+
+ def getObject: Any = {
+ if (lazyObj == null)
+ try
+ lazyObj = U.mkObject(objClassName)
+ catch {
+ // TODO:
+ case e: Throwable => throw new NCE(s"Error initializing object of type: $objClassName", e)
+ }
+
+ lazyObj
+ }
+ }
+
/**
* Gives a list of JAR files at given path.
*
@@ -668,7 +695,7 @@
val mf = makeModelFactory(mft)
mf.initialize(Config.modelFactoryProps.getOrElse(Map.empty[String, String]).asJava)
-
+
mf
case None => new NCBasicModelFactory
@@ -987,7 +1014,7 @@
@throws[NCE]
private def mkChunk(mdl: NCModel, chunk: String): NCProbeSynonymChunk = {
def stripSuffix(fix: String, s: String): String = s.slice(fix.length, s.length - fix.length)
-
+
val mdlId = mdl.getId
// Regex synonym.
@@ -1087,14 +1114,14 @@
s"#${argIdx + (if (cxtFirstParam) 1 else 0)} of ${method2Str(mtd)}"
/**
- *
- * @param mtd
+ * @param mo
* @param mdl
* @param intent
*/
@throws[NCE]
- private def prepareCallback(mtd: Method, mdl: NCModel, intent: NCIdlIntent): Callback = {
+ private def prepareCallback(mo: MethodOwner, mdl: NCModel, intent: NCIdlIntent): Callback = {
val mdlId = mdl.getId
+ val mtd = mo.method
// Checks method result type.
if (mtd.getReturnType != CLS_QRY_RES)
@@ -1195,13 +1222,11 @@
checkMinMax(mdl, mtd, tokParamTypes, termIds.map(allLimits), ctxFirstParam)
Callback(
- mtd.toString,
- mtd.getDeclaringClass.getName,
- mtd.getName,
+ mtd,
(ctx: NCIntentMatch) => {
invoke(
- mtd,
- mdl,
+ mdl.getId,
+ mo,
(
(if (ctxFirstParam) Seq(ctx)
else Seq.empty) ++
@@ -1213,29 +1238,26 @@
}
/**
- *
- * @param mtd
- * @param mdl
+ * @param mdlId
+ * @param mo
* @param args
*/
@throws[NCE]
- private def invoke(mtd: Method, mdl: NCModel, args: Array[AnyRef]): NCResult = {
- val mdlId = mdl.getId
+ private def invoke(mdlId: String, mo: MethodOwner, args: Array[AnyRef]): NCResult = {
+ val obj = if (Modifier.isStatic(mo.method.getModifiers)) null else mo.getObject
- val obj = if (Modifier.isStatic(mtd.getModifiers)) null else mdl
-
- var flag = mtd.canAccess(obj)
+ var flag = mo.method.canAccess(obj)
try {
if (!flag) {
- mtd.setAccessible(true)
+ mo.method.setAccessible(true)
flag = true
}
else
flag = false
- mtd.invoke(obj, args: _*).asInstanceOf[NCResult]
+ mo.method.invoke(obj, args: _*).asInstanceOf[NCResult]
}
catch {
case e: InvocationTargetException => e.getTargetException match {
@@ -1245,25 +1267,25 @@
case e: Throwable =>
throw new NCE(s"Intent callback invocation error [" +
s"mdlId=$mdlId, " +
- s"callback=${method2Str(mtd)}" +
+ s"callback=${method2Str(mo.method)}" +
s"]", e)
}
case e: Throwable =>
throw new NCE(s"Unexpected intent callback invocation error [" +
s"mdlId=$mdlId, " +
- s"callback=${method2Str(mtd)}" +
+ s"callback=${method2Str(mo.method)}" +
s"]", e)
}
finally
if (flag)
try
- mtd.setAccessible(false)
+ mo.method.setAccessible(false)
catch {
case e: SecurityException =>
throw new NCE(s"Access or security error in intent callback [" +
s"mdlId=$mdlId, " +
- s"callback=${method2Str(mtd)}" +
+ s"callback=${method2Str(mo.method)}" +
s"]", e)
}
}
@@ -1498,18 +1520,24 @@
* @param o Object.
* @return Methods.
*/
- private def getAllMethods(o: AnyRef): Set[Method] = {
- val claxx = o.getClass
+ private def getAllMethods(o: AnyRef): Set[Method] = getAllMethods(o.getClass)
- (claxx.getDeclaredMethods ++ claxx.getMethods).toSet
- }
-
+ /**
+ * Gets its own methods including private and accessible from parents.
+ *
+ * @param claxx Class.
+ * @return Methods.
+ */
+ private def getAllMethods(claxx: Class[_]): Set[Method] = (claxx.getDeclaredMethods ++ claxx.getMethods).toSet
+
/**
*
* @param mdl
*/
@throws[NCE]
private def scanIntents(mdl: NCModel): Set[Intent] = {
+ val cl = Thread.currentThread().getContextClassLoader
+
val mdlId = mdl.getId
val intentDecls = mutable.Buffer.empty[NCIdlIntent]
val intents = mutable.Buffer.empty[Intent]
@@ -1517,58 +1545,63 @@
// First, get intent declarations from the JSON/YAML file, if any.
mdl match {
case adapter: NCModelFileAdapter =>
- intentDecls ++= adapter
- .getIntents
- .asScala
- .flatMap(NCIdlCompiler.compileIntents(_, mdl, mdl.getOrigin))
-
+ intentDecls ++= adapter.getIntents.asScala.flatMap(NCIdlCompiler.compileIntents(_, mdl, mdl.getOrigin))
case _ => ()
}
+ def processClass(cls: Class[_]): Unit =
+ if (cls != null)
+ try
+ for (
+ ann <- cls.getAnnotationsByType(CLS_INTENT);
+ intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, cls.getName)
+ )
+ if (intentDecls.exists(_.id == intent.id))
+ throw new NCE(s"Duplicate intent ID [" +
+ s"mdlId=$mdlId, " +
+ s"origin=${mdl.getOrigin}, " +
+ s"class=$cls, " +
+ s"id=${intent.id}" +
+ s"]")
+ else
+ intentDecls += intent
+ catch {
+ case _: ClassNotFoundException => throw new NCE(s"Failed to scan class for @NCIntent annotation: $cls")
+ }
+
// Second, scan class for class-level @NCIntent annotations (intent declarations).
- val mdlCls = mdl.meta[String](MDL_META_MODEL_CLASS_KEY)
+ processClass(Class.forName(mdl.meta[String](MDL_META_MODEL_CLASS_KEY)))
- if (mdlCls != null) {
- try {
- val cls = Class.forName(mdlCls)
+ def processMethod(mo: MethodOwner): Unit = {
+ val m = mo.method
- for (ann <- cls.getAnnotationsByType(CLS_INTENT); intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, mdlCls))
- if (intentDecls.exists(_.id == intent.id))
- throw new NCE(s"Duplicate intent ID [" +
- s"mdlId=$mdlId, " +
- s"origin=${mdl.getOrigin}, " +
- s"class=$mdlCls, " +
- s"id=${intent.id}" +
- s"]")
- else
- intentDecls += intent
- }
- catch {
- case _: ClassNotFoundException => throw new NCE(s"Failed to scan class for @NCIntent annotation: $mdlCls")
- }
- }
-
- // Third, scan all methods for intent-callback bindings.
- for (m <- getAllMethods(mdl)) {
val mtdStr = method2Str(m)
- def bindIntent(intent: NCIdlIntent, cb: Callback): Unit = {
+ def bindIntent(intent: NCIdlIntent, cb: Callback): Unit =
if (intents.exists(i => i._1.id == intent.id && i._2.id != cb.id))
throw new NCE(s"The intent cannot be bound to more than one callback [" +
s"mdlId=$mdlId, " +
s"origin=${mdl.getOrigin}, " +
- s"class=$mdlCls, " +
+ s"class=${mo.objClassName}, " +
s"intentId=${intent.id}" +
s"]")
else {
intentDecls += intent
- intents += (intent -> prepareCallback(m, mdl, intent))
+ intents += (intent -> prepareCallback(mo, mdl, intent))
}
- }
+
+ def existsForOtherMethod(id: String): Boolean =
+ intents.find(_._1.id == id) match {
+ case Some((_, cb)) => cb.method != m
+ case None => false
+ }
// Process inline intent declarations by @NCIntent annotation.
- for (ann <- m.getAnnotationsByType(CLS_INTENT); intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, mtdStr))
- if (intentDecls.exists(_.id == intent.id) || intents.exists(_._1.id == intent.id))
+ for (
+ ann <- m.getAnnotationsByType(CLS_INTENT);
+ intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, mtdStr)
+ )
+ if (intentDecls.exists(_.id == intent.id && existsForOtherMethod(intent.id)))
throw new NCE(s"Duplicate intent ID [" +
s"mdlId=$mdlId, " +
s"origin=${mdl.getOrigin}, " +
@@ -1576,25 +1609,81 @@
s"id=${intent.id}" +
s"]")
else
- bindIntent(intent, prepareCallback(m, mdl, intent))
+ bindIntent(intent, prepareCallback(mo, mdl, intent))
// Process intent references from @NCIntentRef annotation.
for (ann <- m.getAnnotationsByType(CLS_INTENT_REF)) {
val refId = ann.value().trim
intentDecls.find(_.id == refId) match {
- case Some(intent) => bindIntent(intent, prepareCallback(m, mdl, intent))
+ case Some(intent) => bindIntent(intent, prepareCallback(mo, mdl, intent))
case None => throw new NCE(
s"""@NCIntentRef("$refId") references unknown intent ID [""" +
s"mdlId=$mdlId, " +
s"origin=${mdl.getOrigin}, " +
s"refId=$refId, " +
s"callback=$mtdStr" +
- s"]")
+ s"]"
+ )
}
}
}
+ // Third, scan all methods for intent-callback bindings.
+ for (m <- getAllMethods(mdl))
+ processMethod(MethodOwner(method = m, objClassName = null, obj = mdl))
+
+ /**
+ *
+ * @param clazz
+ * @param getReferences
+ * @tparam T
+ */
+ def scanAdditionalClasses[T <: Annotation](clazz: Class[T], getReferences: T => Seq[Class[_]]): Unit = {
+ val anns = mdl.getClass.getAnnotationsByType(clazz)
+
+ if (anns != null && anns.nonEmpty) {
+ val refs = getReferences(anns.head)
+
+ if (refs == null || refs.isEmpty)
+ throw new NCE(
+ s"Additional reference in @${clazz.getSimpleName} annotation is empty [" +
+ s"mdlId=$mdlId, " +
+ s"origin=${mdl.getOrigin}" +
+ s"]"
+ )
+
+ for (ref <- refs if !Modifier.isAbstract(ref.getModifiers)) {
+ processClass(ref)
+
+ for (m <- getAllMethods(ref))
+ processMethod(MethodOwner(method = m, objClassName = ref.getName, obj = null))
+ }
+ }
+ }
+
+ // Process @NCModelAddClasses annotation.
+ scanAdditionalClasses(CLS_MDL_CLS_REF, (a: NCModelAddClasses) => a.value())
+
+ // Process @NCModelAddPackages annotation.
+ scanAdditionalClasses(
+ CLS_MDL_PKGS_REF,
+ (a: NCModelAddPackage) =>
+ a.value().flatMap(p => {
+ if (cl.getDefinedPackage(p) == null)
+ throw new NCE(
+ s"Invalid additional references in @${CLS_MDL_PKGS_REF.getSimpleName} annotation [" +
+ s"mdlId=$mdlId, " +
+ s"origin=${mdl.getOrigin}, " +
+ s"package=$p" +
+ s"]"
+ )
+
+ //noinspection UnstableApiUsage
+ ClassPath.from(cl).getTopLevelClassesRecursive(p).asScala.map(_.load())
+ })
+ )
+
val unusedIntents = intentDecls.filter(i => !intents.exists(_._1.id == i.id))
if (unusedIntents.nonEmpty)
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/model/NCIntentSampleSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/model/NCIntentSampleSpec.scala
index 5e14b07..2efbbd2 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/model/NCIntentSampleSpec.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/model/NCIntentSampleSpec.scala
@@ -19,7 +19,7 @@
import org.apache.nlpcraft.NCTestElement
import org.apache.nlpcraft.model.tools.test.NCTestAutoModelValidator
-import org.junit.jupiter.api.{Assertions, Test}
+import org.junit.jupiter.api.Test
import java.util
import scala.language.implicitConversions
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCModelNestedSamplesSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCModelNestedSamplesSpec.scala
new file mode 100644
index 0000000..0295983
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCModelNestedSamplesSpec.scala
@@ -0,0 +1,64 @@
+/*
+ * 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.probe.mgrs.deploy
+
+import org.apache.nlpcraft.model.tools.embedded.NCEmbeddedProbe
+import org.apache.nlpcraft.model.{NCIntent, NCIntentSample, NCModelAdapter, NCResult}
+import org.apache.nlpcraft.probe.mgrs.model.NCModelManager
+import org.junit.jupiter.api.Test
+
+/**
+ *
+ */
+class NCModelNested extends NCModelAdapter("nlpcraft.samples.test.mdl", "Test Model", "1.0") {
+ /**
+ *
+ * @return
+ */
+ @NCIntentSample(Array("a"))
+ @NCIntent("intent=nested term={tok_id() == 'a'}*")
+ def m(): NCResult = NCResult.text("OK")
+}
+
+/**
+ *
+ */
+class NCModelWrapper extends NCModelNested
+
+/**
+ *
+ */
+class NCModelNestedSamplesSpec {
+ /**
+ *
+ */
+ @Test
+ def test(): Unit = {
+ try {
+ NCEmbeddedProbe.start("nlpcraft.conf", java.util.Collections.singleton(classOf[NCModelWrapper].getName))
+
+ val mdls = NCModelManager.getAllModels()
+
+ require(mdls.size == 1)
+
+ require(mdls.head.samples.nonEmpty)
+ }
+ finally
+ NCEmbeddedProbe.stop()
+ }
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCModelReferencesSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCModelReferencesSpec.scala
new file mode 100644
index 0000000..35af10e
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCModelReferencesSpec.scala
@@ -0,0 +1,123 @@
+/*
+ * 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.probe.mgrs.deploy
+
+import org.apache.nlpcraft.model.{NCElement, NCModelAdapter, NCModelAddPackage}
+import org.apache.nlpcraft.{NCTestContext, NCTestElement, NCTestEnvironment}
+import org.junit.jupiter.api.Test
+
+import java.util
+
+/**
+ *
+ */
+@NCTestEnvironment(model = classOf[org.apache.nlpcraft.probe.mgrs.deploy.scalatest.NCModelClassesWrapper], startClient = true)
+class NCModelClassesWrapperScalaSpec extends NCTestContext {
+ /**
+ *
+ */
+ @Test
+ def test(): Unit = {
+ checkIntent("scalaClass", "scalaClass")
+ checkIntent("scalaStatic", "scalaStatic")
+ checkFail("javaClass")
+ checkFail("javaStatic")
+ }
+}
+
+/**
+ *
+ */
+@NCTestEnvironment(model = classOf[org.apache.nlpcraft.probe.mgrs.deploy.scalatest.NCModelPackagesWrapper], startClient = true)
+class NCModelPackagesWrapperScalaSpec extends NCTestContext {
+ /**
+ *
+ */
+ @Test
+ def test(): Unit = {
+ checkIntent("scalaClass", "scalaClass")
+ checkIntent("scalaStatic", "scalaStatic")
+ checkFail("javaClass")
+ checkFail("javaStatic")
+ }
+}
+
+/**
+ *
+ */
+@NCTestEnvironment(model = classOf[org.apache.nlpcraft.probe.mgrs.deploy.javatest.NCModelClassesWrapper], startClient = true)
+class NCModelClassesWrapperJavaSpec extends NCTestContext {
+ /**
+ *
+ */
+ @Test
+ def test(): Unit = {
+ checkIntent("javaClass", "javaClass")
+ checkIntent("javaStatic", "javaStatic")
+ checkFail("scalaClass")
+ checkFail("scalaStatic")
+ }
+}
+
+/**
+ *
+ */
+@NCTestEnvironment(model = classOf[org.apache.nlpcraft.probe.mgrs.deploy.javatest.NCModelPackagesWrapper], startClient = true)
+class NCModelPackagesWrapperJavaSpec extends NCTestContext {
+ /**
+ *
+ */
+ @Test
+ def test(): Unit = {
+ checkIntent("javaClass", "javaClass")
+ checkIntent("javaStatic", "javaStatic")
+ checkFail("scalaClass")
+ checkFail("scalaStatic")
+ }
+}
+
+/**
+ *
+ */
+@NCModelAddPackage(Array("org.apache.nlpcraft.probe.mgrs.deploy"))
+class NCModelPackagesWrapperMix extends NCModelAdapter("nlpcraft.deploy.test.mix.mdl", "Test Model", "1.0") {
+ override def getElements: util.Set[NCElement] =
+ Set(
+ NCTestElement("scalaClass"),
+ NCTestElement("scalaStatic"),
+ NCTestElement("javaClass"),
+ NCTestElement("javaStatic")
+ )
+}
+
+/**
+ *
+ */
+@NCTestEnvironment(model = classOf[org.apache.nlpcraft.probe.mgrs.deploy.NCModelPackagesWrapperMix], startClient = true)
+class NCModelPackagesWrapperMixSpec extends NCTestContext {
+ /**
+ *
+ */
+ @Test
+ def test(): Unit = {
+ checkIntent("scalaClass", "scalaClass")
+ checkIntent("scalaStatic", "scalaStatic")
+ checkIntent("javaClass", "javaClass")
+ checkIntent("javaStatic", "javaStatic")
+ }
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelClassesWrapper.java b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelClassesWrapper.java
new file mode 100644
index 0000000..d59e43a
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelClassesWrapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.probe.mgrs.deploy.javatest;
+
+import org.apache.nlpcraft.model.NCModelAddClasses;
+
+/**
+ *
+ */
+@NCModelAddClasses({NCNestedClass.class, NCNestedStatic.class})
+public class NCModelClassesWrapper extends NCModelDeploySpecAdapter {
+ // No-op.
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelDeploySpecAdapter.java b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelDeploySpecAdapter.java
new file mode 100644
index 0000000..b4ab85a
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelDeploySpecAdapter.java
@@ -0,0 +1,40 @@
+/*
+ * 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.probe.mgrs.deploy.javatest;
+
+import org.apache.nlpcraft.model.NCElement;
+import org.apache.nlpcraft.model.NCModelAdapter;
+
+import java.util.Set;
+
+/**
+ *
+ */
+class NCModelDeploySpecAdapter extends NCModelAdapter {
+ /**
+ *
+ */
+ public NCModelDeploySpecAdapter() {
+ super("nlpcraft.deploy.test.java", "Test Model", "1.0");
+ }
+
+ @Override
+ public Set<NCElement> getElements() {
+ return Set.of((NCElement) () -> "javaClass", (NCElement) () -> "javaStatic");
+ }
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelPackagesWrapper.java b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelPackagesWrapper.java
new file mode 100644
index 0000000..a03fcb6
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCModelPackagesWrapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.probe.mgrs.deploy.javatest;
+
+import org.apache.nlpcraft.model.NCModelAddPackage;
+
+/**
+ *
+ */
+@NCModelAddPackage("org.apache.nlpcraft.probe.mgrs.deploy.javatest")
+public class NCModelPackagesWrapper extends NCModelDeploySpecAdapter {
+ // No-op.
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCNestedClass.java b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCNestedClass.java
new file mode 100644
index 0000000..131f02e
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCNestedClass.java
@@ -0,0 +1,31 @@
+/*
+ * 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.probe.mgrs.deploy.javatest;
+
+import org.apache.nlpcraft.model.NCIntent;
+import org.apache.nlpcraft.model.NCResult;
+
+/**
+ *
+ */
+public class NCNestedClass {
+ @NCIntent("intent=javaClass term={tok_id() == 'javaClass'}")
+ public NCResult m() {
+ return NCResult.text("OK");
+ }
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCNestedStatic.java b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCNestedStatic.java
new file mode 100644
index 0000000..a91faa9
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/javatest/NCNestedStatic.java
@@ -0,0 +1,31 @@
+/*
+ * 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.probe.mgrs.deploy.javatest;
+
+import org.apache.nlpcraft.model.NCIntent;
+import org.apache.nlpcraft.model.NCResult;
+
+/**
+ *
+ */
+public class NCNestedStatic {
+ @NCIntent("intent=javaStatic term={tok_id() == 'javaStatic'}")
+ public static NCResult m() {
+ return NCResult.text("OK");
+ }
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelClassesWrapper.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelClassesWrapper.scala
new file mode 100644
index 0000000..6443deb
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelClassesWrapper.scala
@@ -0,0 +1,26 @@
+/*
+ * 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.probe.mgrs.deploy.scalatest
+
+import org.apache.nlpcraft.model.NCModelAddClasses
+
+/**
+ *
+ */
+@NCModelAddClasses(Array(classOf[NCNestedClass], classOf[NCNestedStatic.type]))
+class NCModelClassesWrapper extends NCModelDeploySpecAdapter
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelDeploySpecAdapter.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelDeploySpecAdapter.scala
new file mode 100644
index 0000000..ba605e6
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelDeploySpecAdapter.scala
@@ -0,0 +1,31 @@
+/*
+ * 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.probe.mgrs.deploy.scalatest
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCModelAdapter}
+
+import java.util
+import scala.language.implicitConversions
+
+/**
+ *
+ */
+class NCModelDeploySpecAdapter extends NCModelAdapter("nlpcraft.deploy.test.scala", "Test Model", "1.0") {
+ override def getElements: util.Set[NCElement] = Set(NCTestElement("scalaClass"), NCTestElement("scalaStatic"))
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelPackagesWrapper.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelPackagesWrapper.scala
new file mode 100644
index 0000000..5bff402
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCModelPackagesWrapper.scala
@@ -0,0 +1,26 @@
+/*
+ * 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.probe.mgrs.deploy.scalatest
+
+import org.apache.nlpcraft.model.NCModelAddPackage
+
+/**
+ *
+ */
+@NCModelAddPackage(Array("org.apache.nlpcraft.probe.mgrs.deploy.scalatest"))
+class NCModelPackagesWrapper extends NCModelDeploySpecAdapter
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCNestedClass.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCNestedClass.scala
new file mode 100644
index 0000000..352777b
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCNestedClass.scala
@@ -0,0 +1,31 @@
+/*
+ * 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.probe.mgrs.deploy.scalatest
+
+import org.apache.nlpcraft.model.{NCIntent, NCResult}
+
+/**
+ *
+ */
+class NCNestedClass {
+ /**
+ *
+ */
+ @NCIntent("intent=scalaClass term={tok_id() == 'scalaClass'}")
+ def m(): NCResult = NCResult.text("OK")
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCNestedStatic.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCNestedStatic.scala
new file mode 100644
index 0000000..9790efb
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/scalatest/NCNestedStatic.scala
@@ -0,0 +1,32 @@
+/*
+ * 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.probe.mgrs.deploy.scalatest
+
+import org.apache.nlpcraft.model.{NCIntent, NCResult}
+
+/**
+ *
+ */
+object NCNestedStatic {
+ /**
+ *
+ * @return
+ */
+ @NCIntent("intent=scalaStatic term={tok_id() == 'scalaStatic'}")
+ def m(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file