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(

Reply via email to