You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nlpcraft.apache.org by se...@apache.org on 2021/09/11 06:40:12 UTC
[incubator-nlpcraft] branch master updated: NCElement definition
annotations added.
This is an automated email from the ASF dual-hosted git repository.
sergeykamov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git
The following commit(s) were added to refs/heads/master by this push:
new 6b12761 NCElement definition annotations added.
6b12761 is described below
commit 6b12761177ba8b60601c730e4721eb9127f7baa6
Author: Sergey Kamov <sk...@gmail.com>
AuthorDate: Sat Sep 11 09:34:39 2021 +0300
NCElement definition annotations added.
---
.../org/apache/nlpcraft/model/NCAddElement.java | 51 ++++
.../apache/nlpcraft/model/NCAddElementClass.java | 51 ++++
.../probe/mgrs/deploy/NCDeployManager.scala | 339 +++++++++++++++++----
.../apache/nlpcraft/probe/mgrs/deploy/readme.txt | 18 ++
.../mgrs/deploy1/NCElementAnnotationsSpec.scala | 122 ++++++++
.../mgrs/deploy1/pack/NCElementAnnotations2.scala | 34 +++
.../apache/nlpcraft/probe/mgrs/deploy1/readme.txt | 18 ++
7 files changed, 567 insertions(+), 66 deletions(-)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElement.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElement.java
new file mode 100644
index 0000000..b6aadfd
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElement.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Documented;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+// TODO: json or yaml
+@Documented
+@Retention(value=RUNTIME)
+@Target(value=METHOD)
+@Repeatable(NCAddElement.NCAddElementList.class)
+public @interface NCAddElement {
+ /**
+ * ID of the intent term.
+ *
+ * @return ID of the intent term.
+ */
+ String value();
+
+ /**
+ *
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(value=METHOD)
+ @Documented
+ @interface NCAddElementList {
+ NCAddElement[] value();
+ }
+}
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElementClass.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElementClass.java
new file mode 100644
index 0000000..fa0d345
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElementClass.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Documented;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+// TODO: class name
+@Documented
+@Retention(value=RUNTIME)
+@Target(value=METHOD)
+@Repeatable(NCAddElementClass.NCAddElementClassList.class)
+public @interface NCAddElementClass {
+ /**
+ * ID of the intent term.
+ *
+ * @return ID of the intent term.
+ */
+ Class<?> value();
+
+ /**
+ *
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(value=METHOD)
+ @Documented
+ @interface NCAddElementClassList {
+ NCAddElementClass[] 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 fddc682..490b62d 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,9 @@
package org.apache.nlpcraft.probe.mgrs.deploy
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.google.common.reflect.ClassPath
import java.io._
@@ -35,17 +38,19 @@ import org.apache.nlpcraft.common.nlp.core.{NCNlpCoreManager, NCNlpPorterStemmer
import org.apache.nlpcraft.common.util.NCUtils.{IDL_FIX, REGEX_FIX}
import org.apache.nlpcraft.model._
import org.apache.nlpcraft.model.factories.basic.NCBasicModelFactory
+import org.apache.nlpcraft.model.impl.json.NCElementJson
+import org.apache.nlpcraft.model.intent._
import org.apache.nlpcraft.model.intent.compiler.NCIdlCompiler
import org.apache.nlpcraft.model.intent.solver.NCIntentSolver
-import org.apache.nlpcraft.model.intent._
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 java.util.Optional
import scala.util.Using
import scala.compat.java8.OptionConverters._
import scala.collection.mutable
-import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsScala}
+import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala, SeqHasAsJava, SetHasAsJava, SetHasAsScala}
import scala.util.control.Exception._
/**
@@ -63,6 +68,8 @@ object NCDeployManager extends NCService {
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]
+ private final val CLS_ELEM_DEF = classOf[NCAddElement]
+ private final val CLS_ELEM_DEF_CLASS = classOf[NCAddElementClass]
// Java and scala lists.
private final val CLS_SCALA_SEQ = classOf[Seq[_]]
@@ -92,6 +99,13 @@ object NCDeployManager extends NCService {
private final val SEPARATORS = Seq('?', ',', '.', '-', '!')
private final val SUSP_SYNS_CHARS = Seq("?", "*", "+")
+ private val MAPPER_YAML = new ObjectMapper(new YAMLFactory)
+ private val MAPPER_JSON = new ObjectMapper
+
+ MAPPER_JSON.enable(JsonParser.Feature.ALLOW_COMMENTS)
+ MAPPER_JSON.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ MAPPER_YAML.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+
@volatile private var data: mutable.ArrayBuffer[NCProbeModel] = _
@volatile private var mdlFactory: NCModelFactory = _
@@ -170,29 +184,96 @@ object NCDeployManager extends NCService {
val mdlId = mdl.getId
- for (elm <- mdl.getElements.asScala) {
+ val annElems = scanElementsAnnotations(mdl)
+
+ val wrappedMdl =
+ if (annElems.nonEmpty)
+ new NCModel {
+ private val allElems =
+ (annElems ++ (if (mdl.getElements == null) Set.empty else mdl.getElements.asScala)).
+ asJava
+
+ // One wrapped method.
+ override def getElements: util.Set[NCElement] = allElems
+
+ // Other methods delegated.
+ override def getId: String = mdl.getId
+ override def getName: String = mdl.getName
+ override def getVersion: String = mdl.getVersion
+
+ override def onParsedVariant(`var`: NCVariant): Boolean = mdl.onParsedVariant(`var`)
+ override def onContext(ctx: NCContext): NCResult = mdl.onContext(ctx)
+ override def onMatchedIntent(ctx: NCIntentMatch): Boolean = mdl.onMatchedIntent(ctx)
+ override def onResult(ctx: NCIntentMatch, res: NCResult): NCResult = mdl.onResult(ctx, res)
+ override def onRejection(ctx: NCIntentMatch, e: NCRejection): NCResult = mdl.onRejection(ctx, e)
+ override def onError(ctx: NCContext, e: Throwable): NCResult = mdl.onError(ctx, e)
+ override def getDescription: String = mdl.getDescription
+ override def getOrigin: String = mdl.getOrigin
+ override def getMaxUnknownWords: Int = mdl.getMaxUnknownWords
+ override def getMaxFreeWords: Int = mdl.getMaxFreeWords
+ override def getMaxSuspiciousWords: Int = mdl.getMaxSuspiciousWords
+ override def getMinWords: Int = mdl.getMinWords
+ override def getMaxWords: Int = mdl.getMaxWords
+ override def getMinTokens: Int = mdl.getMinTokens
+ override def getMaxTokens: Int = mdl.getMaxTokens
+ override def getMinNonStopwords: Int = mdl.getMinNonStopwords
+ override def isNonEnglishAllowed: Boolean = mdl.isNonEnglishAllowed
+ override def isNotLatinCharsetAllowed: Boolean = mdl.isNotLatinCharsetAllowed
+ override def isSwearWordsAllowed: Boolean = mdl.isSwearWordsAllowed
+ override def isNoNounsAllowed: Boolean = mdl.isNoNounsAllowed
+ override def isPermutateSynonyms: Boolean = mdl.isPermutateSynonyms
+ override def isDupSynonymsAllowed: Boolean = mdl.isDupSynonymsAllowed
+ override def getMaxTotalSynonyms: Int = mdl.getMaxTotalSynonyms
+ override def isNoUserTokensAllowed: Boolean = mdl.isNoUserTokensAllowed
+ override def isSparse: Boolean = mdl.isSparse
+ override def getMetadata: util.Map[String, AnyRef] = mdl.getMetadata
+ override def getAdditionalStopWords: util.Set[String] = mdl.getAdditionalStopWords
+ override def getExcludedStopWords: util.Set[String] = mdl.getExcludedStopWords
+ override def getSuspiciousWords: util.Set[String] = mdl.getSuspiciousWords
+ override def getMacros: util.Map[String, String] = mdl.getMacros
+ override def getParsers: util.List[NCCustomParser] = mdl.getParsers
+ override def getEnabledBuiltInTokens: util.Set[String] = mdl.getEnabledBuiltInTokens
+ override def getAbstractTokens: util.Set[String] = mdl.getAbstractTokens
+ override def getMaxElementSynonyms: Int = mdl.getMaxElementSynonyms
+ override def isMaxSynonymsThresholdError: Boolean = mdl.isMaxSynonymsThresholdError
+ override def getConversationTimeout: Long = mdl.getConversationTimeout
+ override def getConversationDepth: Int = mdl.getConversationDepth
+ override def getRestrictedCombinations: util.Map[String, util.Set[String]] =
+ mdl.getRestrictedCombinations
+
+ override def onInit(): Unit = mdl.onInit()
+ override def onDiscard(): Unit = mdl.onDiscard()
+
+ override def metaOpt[T](prop: String): Optional[T] = mdl.metaOpt(prop)
+ override def meta[T](prop: String): T = mdl.meta(prop)
+ override def metax[T](prop: String): T = mdl.metax(prop)
+ override def meta[T](prop: String, dflt: T): T = mdl.meta(prop, dflt)
+ }
+ else
+ mdl
+
+ for (elm <- wrappedMdl.getElements.asScala)
if (!elm.getId.matches(ID_REGEX))
throw new NCE(
- s"Model element ID does not match regex [" +
+ s"Model element ID does not match regex [" +
s"mdlId=$mdlId, " +
s"elmId=${elm.getId}, " +
s"regex=$ID_REGEX" +
- s"]"
+ s"]"
)
- }
- checkMacros(mdl)
+ checkMacros(wrappedMdl)
val parser = new NCMacroParser
// Initialize macro parser.
- mdl.getMacros.asScala.foreach(t => parser.addMacro(t._1, t._2))
+ wrappedMdl.getMacros.asScala.foreach(t => parser.addMacro(t._1, t._2))
- for (elm <- mdl.getElements.asScala)
- checkElement(mdl, elm)
+ for (elm <- wrappedMdl.getElements.asScala)
+ checkElement(wrappedMdl, elm)
- checkElementIdsDups(mdl)
- checkCyclicDependencies(mdl)
+ checkElementIdsDups(wrappedMdl)
+ checkCyclicDependencies(wrappedMdl)
/**
*
@@ -211,9 +292,9 @@ object NCDeployManager extends NCService {
else
NCNlpCoreManager.stem(word)
- val addStopWords = checkAndStemmatize(mdl.getAdditionalStopWords, "additionalStopWords")
- val exclStopWords = checkAndStemmatize(mdl.getExcludedStopWords, "excludedStopWords")
- val suspWords = checkAndStemmatize(mdl.getSuspiciousWords, "suspiciousWord")
+ val addStopWords = checkAndStemmatize(wrappedMdl.getAdditionalStopWords, "additionalStopWords")
+ val exclStopWords = checkAndStemmatize(wrappedMdl.getExcludedStopWords, "excludedStopWords")
+ val suspWords = checkAndStemmatize(wrappedMdl.getSuspiciousWords, "suspiciousWord")
checkStopwordsDups(mdlId, addStopWords, exclStopWords)
@@ -224,10 +305,10 @@ object NCDeployManager extends NCService {
def sparse(syns: Set[SynonymHolder], sp: Boolean): Set[SynonymHolder] = syns.filter(s => ok(s.syn.sparse, sp))
var cnt = 0
- val maxCnt = mdl.getMaxTotalSynonyms
+ val maxCnt = wrappedMdl.getMaxTotalSynonyms
// Process and check elements.
- for (elm <- mdl.getElements.asScala) {
+ for (elm <- wrappedMdl.getElements.asScala) {
val elmId = elm.getId
// Checks before macros processing.
@@ -242,8 +323,8 @@ object NCDeployManager extends NCService {
s"]"
)
- val sparseElem = elm.isSparse.orElse(mdl.isSparse)
- val permuteElem = elm.isPermutateSynonyms.orElse(mdl.isPermutateSynonyms)
+ val sparseElem = elm.isSparse.orElse(wrappedMdl.isSparse)
+ val permuteElem = elm.isPermutateSynonyms.orElse(wrappedMdl.isPermutateSynonyms)
def addSynonym(
isElementId: Boolean,
@@ -260,7 +341,7 @@ object NCDeployManager extends NCService {
if (syns.add(holder)) {
cnt += 1
- if (mdl.isMaxSynonymsThresholdError && cnt > maxCnt)
+ if (wrappedMdl.isMaxSynonymsThresholdError && cnt > maxCnt)
throw new NCE(s"Too many total synonyms detected [" +
s"mdlId=$mdlId, " +
s"cnt=$cnt, " +
@@ -348,7 +429,7 @@ object NCDeployManager extends NCService {
chunks ++= U.splitTrimFilter(x.substring(start), " ")
- chunks.map(mkChunk(mdl, _))
+ chunks.map(mkChunk(wrappedMdl, _))
}
/**
@@ -392,10 +473,13 @@ object NCDeployManager extends NCService {
val vals =
(if (elm.getValues != null) elm.getValues.asScala else Seq.empty) ++
(
- elm.getValueLoader.asScala match {
- case Some(ldr) => ldr.load(elm).asScala
- case None => Seq.empty
- }
+ if (elm.getValueLoader == null)
+ Seq.empty
+ else
+ elm.getValueLoader.asScala match {
+ case Some(ldr) => ldr.load(elm).asScala
+ case None => Seq.empty
+ }
)
// Add value synonyms.
@@ -436,7 +520,7 @@ object NCDeployManager extends NCService {
}
}
- if (cnt > maxCnt && !mdl.isMaxSynonymsThresholdError)
+ if (cnt > maxCnt && !wrappedMdl.isMaxSynonymsThresholdError)
logger.warn(
s"Too many total synonyms detected [" +
s"mdlId=$mdlId, " +
@@ -445,7 +529,7 @@ object NCDeployManager extends NCService {
s"]")
// Discard value loaders.
- for (elm <- mdl.getElements.asScala)
+ for (elm <- wrappedMdl.getElements.asScala if elm.getValueLoader != null)
elm.getValueLoader.ifPresent(_.onDiscard())
val allAliases = syns
@@ -463,7 +547,7 @@ object NCDeployManager extends NCService {
s"dups=${allAliases.diff(allAliases.distinct).mkString(", ")}" +
s"]")
- val idAliasDups = mdl.getElements.asScala.map(_.getId).intersect(allAliases.toSet)
+ val idAliasDups = wrappedMdl.getElements.asScala.map(_.getId).intersect(allAliases.toSet)
// Check that IDL aliases don't intersect with element IDs.
if (idAliasDups.nonEmpty)
@@ -486,7 +570,7 @@ object NCDeployManager extends NCService {
}
if (dupSyns.nonEmpty) {
- if (mdl.isDupSynonymsAllowed) {
+ if (wrappedMdl.isDupSynonymsAllowed) {
val tbl = NCAsciiTable("Elements", "Dup Synonym")
dupSyns.foreach(row => tbl += (
@@ -506,7 +590,7 @@ object NCDeployManager extends NCService {
}
// Scan for intent annotations in the model class.
- val intents = scanIntents(mdl)
+ val intents = scanIntents(mdl, wrappedMdl)
var solver: NCIntentSolver = null
@@ -516,7 +600,7 @@ object NCDeployManager extends NCService {
case ids if ids.nonEmpty =>
throw new NCE(s"Duplicate intent IDs [" +
s"mdlId=$mdlId, " +
- s"origin=${mdl.getOrigin}, " +
+ s"origin=${wrappedMdl.getOrigin}, " +
s"ids=${ids.mkString(",")}" +
s"]")
case _ => ()
@@ -535,7 +619,7 @@ object NCDeployManager extends NCService {
val simple = idl(syns.toSet, idl = false)
NCProbeModel(
- model = mdl,
+ model = wrappedMdl,
solver = solver,
intents = intents.map(_._1).toSeq,
callbacks = intents.map(kv => (
@@ -552,8 +636,8 @@ object NCDeployManager extends NCService {
addStopWordsStems = addStopWords,
exclStopWordsStems = exclStopWords,
suspWordsStems = suspWords,
- elements = mdl.getElements.asScala.map(elm => (elm.getId, elm)).toMap,
- samples = scanSamples(mdl)
+ elements = wrappedMdl.getElements.asScala.map(elm => (elm.getId, elm)).toMap,
+ samples = scanSamples(wrappedMdl)
)
}
@@ -1534,17 +1618,130 @@ object NCDeployManager extends NCService {
* @param mdl
*/
@throws[NCE]
- private def scanIntents(mdl: NCModel): Set[Intent] = {
- val cl = Thread.currentThread().getContextClassLoader
+ private def scanElementsAnnotations(mdl: NCModel): Set[NCElement] = {
+ val elems = mutable.HashSet.empty[NCElement]
- val mdlId = mdl.getId
+ def scan(claxx: Class[_]): Unit = {
+ val allClassAnns = mutable.ArrayBuffer.empty[NCAddElement]
+
+ def add(anns: Array[NCAddElement]): Unit = if (anns != null) allClassAnns ++= anns.toSeq
+
+ // Class.
+ add(claxx.getAnnotationsByType(CLS_ELEM_DEF))
+
+ // All class's methods.
+ getAllMethods(claxx).foreach(m => add(m.getAnnotationsByType(CLS_ELEM_DEF)))
+
+ allClassAnns.foreach(a => {
+ val body = a.value().strip
+ val expectedJson = body.head == '{'
+
+ val jsElem =
+ try
+ (if (expectedJson) MAPPER_JSON else MAPPER_YAML).readValue(body, classOf[NCElementJson])
+ catch {
+ case e: Exception =>
+ // TODO:
+ throw new NCE(s"Error parsing element[" +
+ s"modelId=${mdl.getId}, " +
+ s"definitionClass=${claxx.getName}, " +
+ s"element='$body', " +
+ s"expectedFormat=${if (expectedJson) "JSON" else "YAML"}" +
+ s"]",
+ e
+ )
+ }
+
+ val elem =
+ new NCElement {
+ private var loader: NCValueLoader = _
+
+ private def nvl[T](arr: Array[T]): Seq[T] = if (arr == null) Seq.empty else arr.toSeq
+
+ override def getId: String = jsElem.getId
+ override def getDescription: String = jsElem.getDescription
+ override def getParentId: String = jsElem.getParentId
+
+ override def getGroups: util.List[String] = nvl(jsElem.getGroups).asJava
+ override def getMetadata: util.Map[String, AnyRef] = jsElem.getMetadata
+ override def getSynonyms: util.List[String] = nvl(jsElem.getSynonyms).asJava
+
+ override def getValues: util.List[NCValue] =
+ nvl(jsElem.getValues).map(v => new NCValue {
+ override def getName: String = v.getName
+ override def getSynonyms: util.List[String] = nvl(v.getSynonyms).asJava
+ }).asJava
+
+ override def getValueLoader: Optional[NCValueLoader] =
+ if (jsElem.getValueLoader != null) {
+ if (loader == null) {
+ loader = U.mkObject(jsElem.getValueLoader)
+
+ loader.onInit()
+ }
+
+ Optional.of(loader)
+ }
+ else
+ Optional.empty()
+
+ override def isPermutateSynonyms: Optional[java.lang.Boolean] =
+ Optional.ofNullable(jsElem.isPermutateSynonyms)
+ override def isSparse: Optional[java.lang.Boolean] =
+ Optional.ofNullable(jsElem.isSparse)
+ }
+
+ elems += elem
+
+ val allClassDefAnns = mutable.ArrayBuffer.empty[NCAddElementClass]
+
+ def addClass(anns: Array[NCAddElementClass]): Unit = if (anns != null) allClassDefAnns ++= anns.toSeq
+
+ addClass(claxx.getAnnotationsByType(CLS_ELEM_DEF_CLASS))
+ getAllMethods(claxx).foreach(m => addClass(m.getAnnotationsByType(CLS_ELEM_DEF_CLASS)))
+
+ allClassDefAnns.foreach(cl =>
+ try
+ elems += cl.value().getDeclaredConstructor().newInstance().asInstanceOf[NCElement]
+ catch {
+ case e: Exception => throw new NCE(s"Failed to instantiate element for: ${cl.value()}", e)
+ }
+ )
+ })
+ }
+
+ val claxx = Class.forName(mdl.meta[String](MDL_META_MODEL_CLASS_KEY))
+
+ scan(claxx)
+
+ val classesRef = claxx.getAnnotationsByType(CLS_MDL_CLS_REF)
+
+ if (classesRef != null && classesRef.nonEmpty)
+ classesRef.head.value().foreach(scan)
+
+ val packRef = claxx.getAnnotationsByType(CLS_MDL_PKGS_REF)
+
+ if (packRef != null && packRef.nonEmpty)
+ packRef.head.value().flatMap(pack => getPackageClasses(mdl, pack)).foreach(scan)
+
+ elems.toSet
+ }
+
+ /**
+ *
+ * @param mdl
+ * @param wrappedMdl
+ */
+ @throws[NCE]
+ private def scanIntents(mdl: NCModel, wrappedMdl: NCModel): Set[Intent] = {
+ val mdlId = wrappedMdl.getId
val intentDecls = mutable.Buffer.empty[NCIdlIntent]
val intents = mutable.Buffer.empty[Intent]
// 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(_, wrappedMdl, wrappedMdl.getOrigin))
case _ => ()
}
@@ -1553,12 +1750,12 @@ object NCDeployManager extends NCService {
try
for (
ann <- cls.getAnnotationsByType(CLS_INTENT);
- intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, cls.getName)
+ intent <- NCIdlCompiler.compileIntents(ann.value(), wrappedMdl, cls.getName)
)
if (intentDecls.exists(_.id == intent.id))
throw new NCE(s"Duplicate intent ID [" +
s"mdlId=$mdlId, " +
- s"origin=${mdl.getOrigin}, " +
+ s"origin=${wrappedMdl.getOrigin}, " +
s"class=$cls, " +
s"id=${intent.id}" +
s"]")
@@ -1580,13 +1777,13 @@ object NCDeployManager extends NCService {
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"origin=${wrappedMdl.getOrigin}, " +
s"class=${mo.objClassName}, " +
s"intentId=${intent.id}" +
s"]")
else {
intentDecls += intent
- intents += (intent -> prepareCallback(mo, mdl, intent))
+ intents += (intent -> prepareCallback(mo, wrappedMdl, intent))
}
def existsForOtherMethod(id: String): Boolean =
@@ -1598,28 +1795,28 @@ object NCDeployManager extends NCService {
// Process inline intent declarations by @NCIntent annotation.
for (
ann <- m.getAnnotationsByType(CLS_INTENT);
- intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, mtdStr)
+ intent <- NCIdlCompiler.compileIntents(ann.value(), wrappedMdl, mtdStr)
)
if (intentDecls.exists(_.id == intent.id && existsForOtherMethod(intent.id)))
throw new NCE(s"Duplicate intent ID [" +
s"mdlId=$mdlId, " +
- s"origin=${mdl.getOrigin}, " +
+ s"origin=${wrappedMdl.getOrigin}, " +
s"callback=$mtdStr, " +
s"id=${intent.id}" +
s"]")
else
- bindIntent(intent, prepareCallback(mo, mdl, intent))
+ bindIntent(intent, prepareCallback(mo, wrappedMdl, 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(mo, mdl, intent))
+ case Some(intent) => bindIntent(intent, prepareCallback(mo, wrappedMdl, intent))
case None => throw new NCE(
s"""@NCIntentRef("$refId") references unknown intent ID [""" +
s"mdlId=$mdlId, " +
- s"origin=${mdl.getOrigin}, " +
+ s"origin=${wrappedMdl.getOrigin}, " +
s"refId=$refId, " +
s"callback=$mtdStr" +
s"]"
@@ -1648,7 +1845,7 @@ object NCDeployManager extends NCService {
throw new NCE(
s"Additional reference in @${clazz.getSimpleName} annotation is empty [" +
s"mdlId=$mdlId, " +
- s"origin=${mdl.getOrigin}" +
+ s"origin=${wrappedMdl.getOrigin}" +
s"]"
)
@@ -1667,23 +1864,7 @@ object NCDeployManager extends NCService {
// Process @NCModelAddPackages annotation.
scanAdditionalClasses(
CLS_MDL_PKGS_REF,
- (a: NCModelAddPackage) =>
- a.value().toIndexedSeq.flatMap(p => {
- //noinspection UnstableApiUsage
- val res = ClassPath.from(cl).getTopLevelClassesRecursive(p).asScala.map(_.load())
-
- // Check should be after classes loading attempt.
- 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"]"
- )
-
- res
- })
+ (a: NCModelAddPackage) => a.value().toIndexedSeq.flatMap(p => getPackageClasses(wrappedMdl, p))
)
val unusedIntents = intentDecls.filter(i => !intents.exists(_._1.id == i.id))
@@ -1691,7 +1872,7 @@ object NCDeployManager extends NCService {
if (unusedIntents.nonEmpty)
logger.warn(s"Intents are unused (have no callback): [" +
s"mdlId=$mdlId, " +
- s"origin=${mdl.getOrigin}, " +
+ s"origin=${wrappedMdl.getOrigin}, " +
s"intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]"
)
@@ -1699,6 +1880,32 @@ object NCDeployManager extends NCService {
}
/**
+ *
+ * @param mdl
+ * @param pack
+ * @return
+ */
+ @throws[NCE]
+ private def getPackageClasses(mdl: NCModel, pack: String): Set[Class[_]] = {
+ val cl = Thread.currentThread().getContextClassLoader
+
+ //noinspection UnstableApiUsage
+ val res = ClassPath.from(cl).getTopLevelClassesRecursive(pack).asScala.map(_.load())
+
+ // Check should be after classes loading attempt.
+ if (cl.getDefinedPackage(pack) == null)
+ throw new NCE(
+ s"Invalid additional references in @${CLS_MDL_PKGS_REF.getSimpleName} annotation [" +
+ s"mdlId=${mdl.getId}, " +
+ s"origin=${mdl.getOrigin}, " +
+ s"package=$pack" +
+ s"]"
+ )
+
+ res.toSet
+ }
+
+ /**
* Scans given model for intent samples.
*
* @param mdl Model to scan.
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/readme.txt b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/readme.txt
new file mode 100644
index 0000000..32b1050
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/readme.txt
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+Do not add new classes here. This package scanned by tests models.
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/NCElementAnnotationsSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/NCElementAnnotationsSpec.scala
new file mode 100644
index 0000000..88ff99f
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/NCElementAnnotationsSpec.scala
@@ -0,0 +1,122 @@
+/*
+ * 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.deploy1
+
+import org.apache.nlpcraft.model.{NCElement, NCAddElement, NCAddElementClass, NCIntent, NCModelAdapter, NCModelAddClasses, NCModelAddPackage, NCResult}
+import org.apache.nlpcraft.{NCTestContext, NCTestElement, NCTestEnvironment}
+import org.junit.jupiter.api.Test
+
+import java.util
+
+@NCAddElement("""{ "id": "e5" }""")
+@NCAddElement("""{ "id": "e6", "synonyms": ["e66"] }""")
+class NCElementAnnotations1 {
+ @NCAddElement("""{ "id": "e7" }""")
+ @NCAddElement("""{ "id": "e8" }""")
+ def x(): Unit = ()
+}
+
+class NCElementAnn1 extends NCElement {
+ override def getId: String = "e12"
+}
+/**
+ *
+ */
+@NCModelAddClasses(Array{classOf[NCElementAnnotations1]})
+@NCModelAddPackage(Array("org.apache.nlpcraft.probe.mgrs.deploy1.pack"))
+@NCAddElement("""{ "id": "e3" }""")
+@NCAddElement("""{ "id": "e4" }""")
+class NCElementAnnotationsSpecModel extends NCModelAdapter("nlpcraft.intents.idl.test", "IDL Test Model", "1.0") {
+ override def getElements: util.Set[NCElement] = Set(NCTestElement("e1"))
+
+ @NCIntent("intent=onE1 term={# == 'e1'}")
+ def onE1(): NCResult = NCResult.text("OK")
+
+ @NCAddElement("""{ "id": "e2" }""")
+ @NCIntent("intent=onE2 term={# == 'e2'}")
+ def onE2(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE3 term={# == 'e3'}")
+ def onE3(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE4 term={# == 'e4'}")
+ def onE4(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE5 term={# == 'e5'}")
+ def onE5(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE6 term={# == 'e6'}")
+ def onE6(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE7 term={# == 'e7'}")
+ def onE7(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE8 term={# == 'e8'}")
+ def onE8(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE9 term={# == 'e9'}")
+ def onE9(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE10 term={# == 'e10'}")
+ def onE10(): NCResult = NCResult.text("OK")
+
+ @NCIntent("intent=onE11 term={# == 'e11'}")
+ def onE11(): NCResult = NCResult.text("OK")
+
+ @NCAddElementClass(classOf[NCElementAnn1])
+ @NCIntent("intent=onE12 term={# == 'e12'}")
+ def onE12(): NCResult = NCResult.text("OK")
+}
+/**
+ *
+ */
+@NCTestEnvironment(model = classOf[NCElementAnnotationsSpecModel], startClient = true)
+class NCElementAnnotationsSpec extends NCTestContext {
+ /**
+ *
+ */
+ @Test
+ def test(): Unit = {
+ // Defined in model.
+ checkIntent("e1", "onE1")
+
+ // Added via annotation to model method.
+ checkIntent("e2", "onE2")
+
+ // Added via annotation to model class.
+ checkIntent("e3", "onE3")
+ checkIntent("e4", "onE4")
+
+ // Added via annotation to class and methods, where class added via NCModelAddClasses.
+ // Multiple annotation tested.
+ // Complex JSON tested.
+ checkIntent("e5", "onE5")
+ checkIntent("e66", "onE6")
+ checkIntent("e7", "onE7")
+ checkIntent("e8", "onE8")
+
+ // Added via annotation to class and method, where class added via NCModelAddPackage.
+ // Complex YAML tested.
+ checkIntent("e9", "onE9")
+ checkIntent("e101", "onE10")
+ checkIntent("e11", "onE11")
+
+ // Added via class annotation (second approach).
+ checkIntent("e12", "onE12")
+ }
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/pack/NCElementAnnotations2.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/pack/NCElementAnnotations2.scala
new file mode 100644
index 0000000..a3e3d0d
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/pack/NCElementAnnotations2.scala
@@ -0,0 +1,34 @@
+/*
+ * 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.deploy1.pack
+
+import org.apache.nlpcraft.model.NCAddElement
+
+@NCAddElement("""{ "id": "e9" }""")
+@NCAddElement(
+ """---
+ id: "e10"
+ synonyms:
+ - "e100"
+ - "e101"
+ """
+)
+class NCElementAnnotations2 {
+ @NCAddElement("""{ "id": "e11" }""")
+ def x(): Unit = ()
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/readme.txt b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/readme.txt
new file mode 100644
index 0000000..32b1050
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/readme.txt
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+Do not add new classes here. This package scanned by tests models.
\ No newline at end of file