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 3e2a2ad WIP.
3e2a2ad is described below
commit 3e2a2ad288767a0c936c7a83ab0a4f5d90292587
Author: Sergey Kamov <[email protected]>
AuthorDate: Fri Jan 28 21:01:11 2022 +0300
WIP.
---
.../scala/org/apache/nlpcraft/NCIntentImport.java | 55 -----
.../nlpcraft/internal/impl/NCModelScanner.scala | 255 ++++++++-------------
.../impl/scan/NCModelIntentsNestedSpec.scala | 2 +-
.../internal/impl/scan/NCTestModelJava.java | 3 +-
.../internal/impl/scan/NCTestModelScala.scala | 7 +-
5 files changed, 97 insertions(+), 225 deletions(-)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCIntentImport.java
b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCIntentImport.java
deleted file mode 100644
index 1faaee1..0000000
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCIntentImport.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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;
-
-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;
-
-/**
- *
- */
-@Retention(value=RUNTIME)
-@Target(value=METHOD)
-@Repeatable(NCIntentImport.NCIntentImportList.class)
-public @interface NCIntentImport {
- /***
- *
- * @return
- */
- String[] value();
-
- /**
- *
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(value=METHOD)
- @Documented
- @interface NCIntentImportList {
- /**
- *
- * @return
- */
- NCIntentImport[] value();
- }
-}
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 45db01c..c17612c 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
@@ -57,7 +57,6 @@ object NCModelScanner extends LazyLogging:
private final val CLS_SAMPLE = classOf[NCIntentSample]
private final val CLS_SAMPLE_REF = classOf[NCIntentSampleRef]
private final val CLS_INTENT_OBJ = classOf[NCIntentObject]
- private final val CLS_INTENT_IMPORT = classOf[NCIntentImport]
// Java and scala lists.
private final val CLS_SCALA_SEQ = classOf[Seq[_]]
@@ -77,11 +76,7 @@ object NCModelScanner extends LazyLogging:
)
private case class CallbackHolder(method: Method, function: NCIntentMatch
=> NCResult)
- private case class IntentHolder(intent: NCIDLIntent, function:
NCIntentMatch => NCResult, samples: Seq[Seq[String]], method: Method)
- private case class ObjectsHolder (
- intentDecls: mutable.Buffer[NCIDLIntent] = mutable.Buffer.empty,
- objects: mutable.Buffer[Object] = mutable.Buffer.empty
- )
+ private case class IntentHolder(intent: NCIDLIntent, function:
NCIntentMatch => NCResult, method: Method)
/**
*
@@ -222,28 +217,36 @@ object NCModelScanner extends LazyLogging:
* @return
*/
private def getFieldObject(mdlId: String, field: Field, obj: Object):
Object =
+ lazy val fStr = field2Str(field)
val fieldObj = if Modifier.isStatic(field.getModifiers) then null else
obj
var flag = field.canAccess(fieldObj)
+ val res =
+ try
+ if !flag then
+ field.setAccessible(true)
- try
- if !flag then
- field.setAccessible(true)
+ flag = true
+ else
+ flag = false
+
+ field.get(fieldObj)
+ catch
+ // TODO: text
+ case e: Throwable => E(s"Unexpected field access error
[mdlId=$mdlId, 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)
+
+ if res == null then
+ throw new NCException(s"Value is null for: $fStr") // TODO: text
+
+ res
- flag = true
- else
- flag = false
- field.get(fieldObj)
- catch
- // TODO: text
- case e: Throwable => E(s"Unexpected field access error
[mdlId=$mdlId, field=${field2Str(field)}]", e)
- finally
- if flag then
- try
- field.setAccessible(false)
- catch
- // TODO: text
- case e: SecurityException => E(s"Access or security error
in field [mdlId=$mdlId, field=${field2Str(field)}]", e)
/**
*
@@ -260,23 +263,9 @@ object NCModelScanner extends LazyLogging:
* @param o Object.
* @return Methods.
*/
- private def getAllMethods(o: AnyRef): Set[Method] =
getAllMethods(o.getClass)
-
- /**
- * 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
-
- /**
- * Gets its own fields including private and accessible from parents.
- *
- * @param claxx Class
- * @return Fields.
- */
- private def getAllFields(claxx: Class[_]): Set[Field] =
(claxx.getDeclaredFields ++ claxx.getFields).toSet
+ private def getAllMethods(o: AnyRef): Set[Method] =
+ val claxx = o.getClass
+ (claxx.getDeclaredMethods ++ claxx.getMethods).toSet
/**
* Gets its own fields including private and accessible from parents.
@@ -284,58 +273,9 @@ object NCModelScanner extends LazyLogging:
* @param o Object.
* @return Fields.
*/
- private def getAllFields(o: AnyRef): Set[Field] = getAllFields(o.getClass)
-
- /**
- *
- * @param a
- * @return
- */
- private def isNullOrEmpty(a: Any): Boolean =
- a == null ||
- (a match
- case s: String => s.strip.isEmpty
- case _ => false
- )
-
- /**
- *
- * @param it
- * @return
- */
- private def isNullOrEmpty(it: Iterable[_]): Boolean = it == null ||
it.isEmpty || it.exists(isNullOrEmpty)
-
- /**
- *
- * @param anns
- * @param origin
- */
- private def emptyError(anns: Iterable[_], origin: String): Unit =
- require(anns != null && anns.nonEmpty)
-
- E(s"Unexpected empty annotation definition
@${anns.head.getClass.getSimpleName} in $origin") // TODO: text
-
- /**
- *
- * @param anns
- * @param getValues
- * @param origin
- * @tparam T
- * @tparam K
- */
- private def checkMultiple[T, K](anns: Iterable[T], getValues: T =>
Iterable[K], origin: => String): Unit =
- if anns.exists(a => a == null || isNullOrEmpty(getValues(a))) then
emptyError(anns, origin)
-
- /**
- *
- * @param anns
- * @param getValue
- * @param origin
- * @tparam T
- * @tparam K
- */
- private def checkSingle[T, K](anns: Iterable[T], getValue: T => K, origin:
=> String): Unit =
- if anns.exists(a => a == null || isNullOrEmpty(getValue(a))) then
emptyError(anns, origin)
+ private def getAllFields(o: AnyRef): Set[Field] =
+ val claxx = o.getClass
+ (claxx.getDeclaredFields ++ claxx.getFields).toSet
import org.apache.nlpcraft.internal.impl.NCModelScanner.*
@@ -350,50 +290,10 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
private final val mdlId = cfg.getId
private final val origin = cfg.getOrigin
- /**
- *
- * @return
- */
- private def scanImportsAndRefs(): ObjectsHolder =
- val h = ObjectsHolder()
-
- def processImports(anns: scala.Array[NCIntentImport], orig: =>
String): Unit =
- if anns.nonEmpty then
- checkMultiple(anns, (a: NCIntentImport) => a.value, orig)
-
- for (
- ann <- anns;
- res <- ann.value;
- intent <-
NCIDLCompiler.compile(NCUtils.readResource(res.strip).mkString("\n"), cfg, res)
- )
- if h.intentDecls.exists(_.id == intent.id) then
- E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin,
resource=$res, id=${intent.id}]")
- h.intentDecls += intent
-
- def scanObject(obj: Object): Unit =
- val claxx = obj.getClass
-
- processImports(claxx.getAnnotationsByType(CLS_INTENT_IMPORT),
claxx.getSimpleName)
-
- for (m <- getAllMethods(claxx))
- processImports(m.getAnnotationsByType(CLS_INTENT_IMPORT),
method2Str(m))
-
- for (f <- getAllFields(claxx))
- processImports(f.getAnnotationsByType(CLS_INTENT_IMPORT),
field2Str(f))
-
- if (f.isAnnotationPresent(CLS_INTENT_OBJ))
- val fieldObj = getFieldObject(mdlId, f, obj)
-
- if fieldObj == null then
- throw new NCException(s"Value is null for:
${field2Str(f)}") // TODO: text
-
- h.objects += fieldObj
- scanObject(fieldObj)
-
- h.objects += mdl
- scanObject(mdl)
-
- h
+ private final val intents = mutable.Buffer.empty[IntentHolder]
+ private final val intentDecls = mutable.Buffer.empty[NCIDLIntent]
+ private final val objs = mutable.Buffer.empty[Object]
+ private final val samples = mutable.HashMap.empty[Method, Map[String,
Seq[Seq[String]]]]
/**
*
@@ -492,50 +392,68 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
/**
*
- * @param intentDecls
* @param intents
+ * @param intent
+ * @param cb
* @param mtd
- * @param obj
*/
- private def processMethod(intentDecls: mutable.Buffer[NCIDLIntent],
intents: mutable.Buffer[IntentHolder], mtd: Method, obj: Object): Unit =
- val mtdStr = method2Str(mtd)
- lazy val samples = scanSamples(mtd)
+ private def checkBind(intents: mutable.Buffer[IntentHolder], intent:
NCIDLIntent, cb: CallbackHolder, mtd: Method): Unit =
+ if intents.exists(i => i._1.id == intent.id && i.method != cb.method)
then
+ E(s"The intent cannot be bound to more than one callback
[mdlId=$mdlId, origin=$origin, class=${getClassName(mtd.getDeclaringClass)},
intentId=${intent.id}]")
+
+ private def bindIntent(intent: NCIDLIntent, cb: CallbackHolder, mtd:
Method): Unit =
+ if intents.exists(i => i._1.id == intent.id && i.method != cb.method)
then
+ E(s"The intent cannot be bound to more than one callback
[mdlId=$mdlId, origin=$origin, class=${getClassName(mtd.getDeclaringClass)},
intentId=${intent.id}]")
+ else
+ intentDecls += intent
+ intents += IntentHolder(intent, cb.function, cb.method)
- def bindIntent(intent: NCIDLIntent, cb: CallbackHolder): Unit =
- if intents.exists(i => i._1.id == intent.id && i.method !=
cb.method) then
- E(s"The intent cannot be bound to more than one callback
[mdlId=$mdlId, origin=$origin, class=${getClassName(mtd.getDeclaringClass)},
intentId=${intent.id}]")
- else
- intentDecls += intent
- intents += IntentHolder(intent, cb.function,
samples.getOrElse(intent.id, Seq.empty), cb.method)
+
+ /**
+ *
+ * @param mtd
+ * @param obj
+ */
+ private def processMethod(mtd: Method, obj: Object): Unit =
+ samples += mtd -> scanSamples(mtd)
def existsForOtherMethod(id: String): Boolean =
intents.find(_.intent.id == id) match
case Some(i) => i.method != mtd
case None => false
- // 1. Process inline intent declarations by @NCIntent annotation.
- val annsIntents = mtd.getAnnotationsByType(CLS_INTENT)
+ val anns = mtd.getAnnotationsByType(CLS_INTENT)
- if annsIntents.nonEmpty then
- checkSingle(annsIntents, (a:NCIntent) => a.value, mtdStr)
+ if anns.nonEmpty then
+ lazy val mtdStr = method2Str(mtd)
- for (ann <- annsIntents; intent <-
NCIDLCompiler.compile(ann.value, cfg, mtdStr))
+ if anns.exists(a => a == null || a.value().strip().isEmpty) then
+ E(s"Unexpected empty annotation definition @NCIntentRef in
$mtdStr") // TODO: text
+
+ for (ann <- anns; intent <- NCIDLCompiler.compile(ann.value, cfg,
mtdStr))
if intentDecls.exists(_.id == intent.id &&
existsForOtherMethod(intent.id)) then
E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin,
callback=$mtdStr, id=${intent.id}]")
else
- bindIntent(intent, prepareCallback(mtd, obj, intent))
+ bindIntent(intent, prepareCallback(mtd, obj, intent), mtd)
- // 2. Process intent references from @NCIntentRef annotation.
- val annRefs = mtd.getAnnotationsByType(CLS_INTENT_REF)
+ /**
+ *
+ * @param mtd
+ * @param obj
+ */
+ private def processMethodRefs(mtd: Method, obj: Object): Unit =
+ val anns = mtd.getAnnotationsByType(CLS_INTENT_REF)
- if annRefs.nonEmpty then
- checkSingle(annRefs, (a:NCIntentRef) => a.value, mtdStr)
+ if anns.nonEmpty then
+ lazy val mtdStr = method2Str(mtd)
+ if anns.exists(a => a == null || a.value().strip().isEmpty) then
+ E(s"Unexpected empty annotation definition @NCIntent in
$mtdStr") // TODO: text
- for (ann <- annRefs)
+ for (ann <- anns)
val refId = ann.value.trim
intentDecls.find(_.id == refId) match
- case Some(intent) => bindIntent(intent,
prepareCallback(mtd, obj, intent))
+ case Some(intent) => bindIntent(intent,
prepareCallback(mtd, obj, intent), mtd)
case None => E(s"@NCIntentRef(\"$refId\") references
unknown intent ID [mdlId=$mdlId, origin=$origin, refId=$refId,
callback=$mtdStr]")
/**
@@ -677,13 +595,22 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
* @return
*/
def scan(): Seq[NCModelIntent] =
- val h = scanImportsAndRefs()
+ // 1. 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)
+ def scan(obj: Object): Unit =
+ objs += obj
+
+ for (m <- getAllMethods(obj)) processMethod(m, obj)
+ for (f <- getAllFields(obj) if
f.isAnnotationPresent(CLS_INTENT_OBJ)) scan(getFieldObject(mdlId, f, obj))
- val intents = mutable.Buffer.empty[IntentHolder]
+ scan(mdl)
- for (obj <- h.objects; mtd <- getAllMethods(obj))
processMethod(h.intentDecls, intents, mtd, obj)
+ // 2. For model and all its references scans each method and finds
intents references (NCIntentRef)
+ for (o <- objs; m <- getAllMethods(o)) processMethodRefs(m, o)
- val unusedIntents = h.intentDecls.filter(i => !intents.exists(_._1.id
== i.id))
+ // 3. Validation.
+ val unusedIntents = intentDecls.filter(i => !intents.exists(_._1.id ==
i.id))
if unusedIntents.nonEmpty then
logger.warn(s"Intents are unused (have no callback):
[mdlId=$mdlId, origin=$origin,
intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]")
@@ -696,4 +623,4 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
else
logger.warn(s"Model has no intent: $mdlId")
- intents.map(i => NCModelIntent(i.intent, i.function, i.samples)).toSeq
\ No newline at end of file
+ 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/NCModelIntentsNestedSpec.scala
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
index 378bfd9..517e99b 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
@@ -32,7 +32,7 @@ class NCModelIntentsNestedSpec:
val nested1: Object = new Object():
@NCIntentObject
val nested2: Object = new Object():
- @NCIntentImport(Array("scan/idl.idl"))
+ @NCIntent("import('scan/idl.idl')")
@NCIntent("intent=intent3 term(x)~{true}")
def intent1(@NCIntentTerm("x") x: NCEntity) = new NCResult()
diff --git
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java
index c407748..046e7bc 100644
---
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java
+++
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java
@@ -18,7 +18,6 @@
package org.apache.nlpcraft.internal.impl.scan;
import org.apache.nlpcraft.NCIntent;
-import org.apache.nlpcraft.NCIntentImport;
import org.apache.nlpcraft.NCIntentRef;
import org.apache.nlpcraft.NCIntentSample;
import org.apache.nlpcraft.NCIntentSampleRef;
@@ -43,7 +42,7 @@ public class NCTestModelJava {
public static NCModel mkModel() {
return
new NCModelAdapter(NCTestConfigJava.CFG,
NCTestConfigJava.EN_PIPELINE) {
- @NCIntentImport({"scan/idl.idl"})
+ @NCIntent("import('scan/idl.idl')")
@NCIntent(
"intent=locInt term(single)~{# == 'id1'} term(list)~{# ==
'id2'}[0,10] term(opt)~{# == 'id3'}?"
)
diff --git
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
index 53858d6..5ec7a2b 100644
---
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
+++
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
@@ -25,7 +25,7 @@ import org.apache.nlpcraft.nlp.util.opennlp.*
*
*/
object NCTestModelScala:
- @NCIntentImport(Array("scan/idl.idl"))
+ @NCIntent("import('scan/idl.idl')")
object NCTestModelScalaObj extends NCTestModelAdapter :
@NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# ==
'id2'}[0,10] term(opt)~{# == 'id3'}?")
@NCIntentSample(Array("What are the least performing categories for
the last quarter?"))
@@ -43,7 +43,8 @@ object NCTestModelScala:
@NCIntentTerm("opt") opt: Option[NCEntity]
): NCResult = new NCResult()
- @NCIntentImport(Array("scan/idl.idl")) class NCTestModelScalaClass extends
NCTestModelAdapter :
+ @NCIntent("import('scan/idl.idl')")
+ class NCTestModelScalaClass extends NCTestModelAdapter :
@NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# ==
'id2'}[0,10] term(opt)~{# == 'id3'}?")
@NCIntentSample(Array("What are the least performing categories for
the last quarter?"))
def intent(
@@ -65,7 +66,7 @@ object NCTestModelScala:
* @return
*/
def mkModel: NCModel = new NCTestModelAdapter() :
- @NCIntentImport(Array("scan/idl.idl"))
+ @NCIntent("import('scan/idl.idl')")
@NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# ==
'id2'}[0,10] term(opt)~{# == 'id3'}?")
@NCIntentSample(Array("What are the least performing categories for
the last quarter?"))
def intent(