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 ca8378a WIP. ca8378a is described below commit ca8378a3ff8a3a5f66dc44b4b8df8344482b72b1 Author: Sergey Kamov <skhdlem...@gmail.com> AuthorDate: Thu Jan 27 15:20:32 2022 +0300 WIP. --- .../internal/impl/NCAnnotationsScanner.scala | 223 ++++++++++++--------- nlpcraft/src/test/resources/scan/idl.idl | 2 +- .../nlpcraft/internal/impl/NCTestModelScala.scala | 67 ------- .../impl/scan/NCAnnotationInvalidArgsSpec.scala | 159 +++++++++++++++ .../impl/scan/NCAnnotationNestedSpec.scala | 58 ++++++ .../NCAnnotationSpec.scala} | 11 +- .../internal/impl/{ => scan}/NCTestModelJava.java | 14 +- .../internal/impl/scan/NCTestModelScala.scala | 88 ++++++++ .../intent/compiler/functions/NCIDLFunctions.scala | 16 +- .../apache/nlpcraft/nlp/util/NCTestEntity.scala | 47 +++++ 10 files changed, 502 insertions(+), 183 deletions(-) diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala index 3625dc7..e1a1b46 100644 --- a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala +++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScanner.scala @@ -18,6 +18,7 @@ package org.apache.nlpcraft.internal.impl import com.typesafe.scalalogging.LazyLogging +import org.apache.commons.lang3.reflect.FieldUtils import org.apache.nlpcraft.* import org.apache.nlpcraft.internal.intent.* import org.apache.nlpcraft.internal.intent.compiler.* @@ -43,7 +44,7 @@ private[internal] case class NCCallback(method: Method, cbFun: Function[NCIntent private[internal] case class NCIntentData(intent: NCIDLIntent, callback: NCCallback, samples: Seq[Seq[String]]) /** - * + * TODO: common comment for annotations usage. Do not apply annotations for unused private methods and fields. Compiler can drop them. */ object NCAnnotationsScanner extends LazyLogging: private final val SEPARATORS = Seq('?', ',', '.', '-', '!') @@ -76,16 +77,6 @@ object NCAnnotationsScanner extends LazyLogging: /** * - * @param method - * @param claxx - * @param obj - */ - private case class MethodOwner(method: Method, obj: Any): - require(method != null && obj != null) - lazy val className: Class[_] = obj.getClass - - /** - * * @param cls * @return */ @@ -100,11 +91,21 @@ object NCAnnotationsScanner extends LazyLogging: /** * + * @param clazz + * @return + */ + private def getClassName(clazz: Class[_]): String = + val cls = clazz.getSimpleName.strip + // Anonymous classes (like `class foo.bar.name$1`) doesn't have simple names. + if cls.nonEmpty then cls else clazz.getName.reverse.takeWhile(_ != '.').reverse + + /** + * * @param mtd * @return */ private def method2Str(mtd: Method): String = - val cls = mtd.getDeclaringClass.getSimpleName + val cls = getClassName(mtd.getDeclaringClass) val name = mtd.getName val args = mtd.getParameters.map(_.getType.getSimpleName).mkString(", ") @@ -116,7 +117,7 @@ object NCAnnotationsScanner extends LazyLogging: * @return */ private def field2Str(f: Field): String = - val cls = f.getDeclaringClass.getSimpleName + val cls = getClassName(f.getDeclaringClass) val name = f.getName s"$cls.$name" @@ -164,45 +165,77 @@ object NCAnnotationsScanner extends LazyLogging: case 1 => util.Optional.of(argList.get(0)) case _ => E(s"Too many entities ($entsCnt) for java.util.Optional @NCIntentTerm annotated argument [mdlId$mdlId, arg=${mkArg()}]") else - // All allowed arguments types already checked, but // TODO: (spec case) - throw new NCException(s"Unexpected callback @NCIntentTerm argument type [mdlId=$mdlId, type=$paramCls, arg=${mkArg()}]") + // All allowed arguments types already checked. + throw new AssertionError(s"Unexpected callback @NCIntentTerm argument type [mdlId=$mdlId, type=$paramCls, arg=${mkArg()}]") } /** * * @param mdlId - * @param mo + * @param method + * @param obj * @param args * @return */ - private def invoke(mdlId: String, mo: MethodOwner, args: scala.Array[AnyRef]): NCResult = - val obj = if Modifier.isStatic(mo.method.getModifiers) then null else mo.obj - var flag = mo.method.canAccess(obj) + private def invoke(mdlId: String, method: Method, obj: Object, args: scala.Array[AnyRef]): NCResult = + val methodObj = if Modifier.isStatic(method.getModifiers) then null else obj + var flag = method.canAccess(methodObj) try if !flag then - mo.method.setAccessible(true) + method.setAccessible(true) flag = true else flag = false - mo.method.invoke(obj, args: _*).asInstanceOf[NCResult] + method.invoke(methodObj, args: _*).asInstanceOf[NCResult] catch case e: InvocationTargetException => e.getTargetException match case cause: NCIntentSkip => throw cause case cause: NCRejection => throw cause case cause: NCException => throw cause - case cause: Throwable => E(s"Intent callback invocation error [mdlId=$mdlId, callback=${method2Str(mo.method)}]", cause) + case cause: Throwable => E(s"Intent callback invocation error [mdlId=$mdlId, callback=${method2Str(method)}]", cause) + + case e: Throwable => E(s"Unexpected intent callback invocation error [mdlId=$mdlId, callback=${method2Str(method)}]", e) + finally + if flag then + try + method.setAccessible(false) + catch + case e: SecurityException => E(s"Access or security error in intent callback [mdlId=$mdlId, callback=${method2Str(method)}]", e) + + /** + * + * @param mdlId + * @param field + * @param obj + * @return + */ + private def getField(mdlId: String, field: Field, obj: Object): Object = + val fieldObj = if Modifier.isStatic(field.getModifiers) then null else obj + var flag = field.canAccess(fieldObj) + + try + if !flag then + field.setAccessible(true) + + flag = true + else + flag = false - case e: Throwable => E(s"Unexpected intent callback invocation error [mdlId=$mdlId, callback=${method2Str(mo.method)}]", e) + 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 - mo.method.setAccessible(false) + field.setAccessible(false) catch - case e: SecurityException => E(s"Access or security error in intent callback [mdlId=$mdlId, callback=${method2Str(mo.method)}]", e) + // TODO: text + case e: SecurityException => E(s"Access or security error in field [mdlId=$mdlId, field=${field2Str(field)}]", e) /** * @@ -305,7 +338,7 @@ import org.apache.nlpcraft.internal.impl.NCAnnotationsScanner.* */ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: private val cfg = mdl.getConfig - private val id = cfg.getId + private val mdlId = cfg.getId private val origin = cfg.getOrigin /** @@ -325,7 +358,7 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: intent <- NCIDLCompiler.compile(NCUtils.readResource(res.strip).mkString("\n"), cfg, res) ) if intentDecls.exists(_.id == intent.id) then - E(s"Duplicate intent ID [mdlId=$id, origin=$origin, resource=$res, id=${intent.id}]") + E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin, resource=$res, id=${intent.id}]") intentDecls += intent def scanClass(obj: Object): Unit = @@ -339,7 +372,7 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: for (f <- getAllFields(claxx)) processImports(f.getAnnotationsByType(CLS_INTENT_IMPORT), field2Str(f)) if (f.isAnnotationPresent(CLS_INTENT_OBJ)) - val ref = f.get(obj) + val ref = getField(mdlId, f, obj) objs += ref scanClass(ref) @@ -358,67 +391,68 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: // Model instance methods processed. for (m <- getAllMethods(mdl)) - processMethod(intentDecls, intents, MethodOwner(m, mdl)) + processMethod(intentDecls, intents, m, mdl) // References classes. We can use static methods or try to initialize class instance using default constructor. for (ref <- refs; m <- getAllMethods(ref)) - processMethod(intentDecls, intents, MethodOwner(m, ref)) + processMethod(intentDecls, intents, m, ref) 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=$id, origin=$origin, intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]") + logger.warn(s"Intents are unused (have no callback): [mdlId=$mdlId, origin=$origin, intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]") - if (intents.nonEmpty) { + if intents.nonEmpty then // Check the uniqueness of intent IDs. NCUtils.getDups(intents.map(_._1).toSeq.map(_.id)) match - case ids if ids.nonEmpty => E(s"Duplicate intent IDs [mdlId=$id, origin=$origin, ids=${ids.mkString(",")}]") + case ids if ids.nonEmpty => E(s"Duplicate intent IDs [mdlId=$mdlId, origin=$origin, ids=${ids.mkString(",")}]") case _ => // No-op. - } else - logger.warn(s"Model has no intent: $id") + logger.warn(s"Model has no intent: $mdlId") intents.toSeq /** * * @param mtd - * @param paramCls + * @param argClasses * @param paramGenTypes * @param ctxFirstParam */ - private def checkTypes(mtd: Method, paramCls: Seq[Class[_]], paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit = - require(paramCls.sizeIs == paramGenTypes.length) + private def checkTypes(mtd: Method, argClasses: Seq[Class[_]], paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit = + require(argClasses.sizeIs == paramGenTypes.length) - paramCls.zip(paramGenTypes).zipWithIndex.foreach { case ((pClass, pGenType), i) => + var warned = false + + argClasses.zip(paramGenTypes).zipWithIndex.foreach { case ((argClass, paramGenType), i) => def mkArg(): String = arg2Str(mtd, i, ctxFirstParam) // Entity. - if pClass == CLS_ENTITY then () // No-op. - else if pClass.isArray then - val compType = pClass.getComponentType + if argClass == CLS_ENTITY then () // No-op. + else if argClass.isArray then + val compType = argClass.getComponentType if compType != CLS_ENTITY then - E(s"Unexpected array element type for @NCIntentTerm annotated argument [mdlId=$id, origin=$origin, type=${class2Str(compType)}, arg=${mkArg()}]") + E(s"Unexpected array element type for @NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, type=${class2Str(compType)}, arg=${mkArg()}]") // Entities collection and optionals. - else if COMP_CLS.contains(pClass) then - pGenType match + else if COMP_CLS.contains(argClass) then + paramGenType match case pt: ParameterizedType => val actTypes = pt.getActualTypeArguments val compTypes = if actTypes == null then Seq.empty else actTypes.toSeq if compTypes.sizeIs != 1 then - E(s"Unexpected generic types count for @NCIntentTerm annotated argument [mdlId=$id, origin=$origin, count=${compTypes.length}, arg=${mkArg()}]") + E(s"Unexpected generic types count for @NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, count=${compTypes.length}, arg=${mkArg()}]") val compType = compTypes.head compType match - // Java, Groovy. + // Java, Scala, Groovy. case _: Class[_] => val genClass = compTypes.head.asInstanceOf[Class[_]] if genClass != CLS_ENTITY then - E(s"Unexpected generic type for @NCIntentTerm annotated argument [mdlId=$id, origin=$origin, type=${class2Str(genClass)}, arg=${mkArg()}]") + E(s"Unexpected generic type for @NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, type=${class2Str(genClass)}, arg=${mkArg()}]") // Kotlin. case _: WildcardType => @@ -428,18 +462,20 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: val upBounds = wildcardType.getUpperBounds if lowBounds.nonEmpty || upBounds.size != 1 || upBounds(0) != CLS_ENTITY then - E(s"Unexpected Kotlin generic type for @NCIntentTerm annotated argument [mdlId=$id, origin=$origin, type=${wc2Str(wildcardType)}, arg=${mkArg()}]") - case _ => E(s"Unexpected generic type for @NCIntentTerm annotated argument [mdlId=$id, origin=$origin, type=${compType.getTypeName}, arg=${mkArg()}]") + E(s"Unexpected Kotlin generic type for @NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, type=${wc2Str(wildcardType)}, arg=${mkArg()}]") + case _ => E(s"Unexpected generic type for @NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, type=${compType.getTypeName}, arg=${mkArg()}]") -// // TODO: scala 3 ParameterizedType doesn't here. -// case _ : Any if pGenType.getTypeName == "scala.collection.immutable.Seq" || -// pGenType.getTypeName == "scala.collection.immutable.List" || -// pGenType.getTypeName == "scala.Option" => // No-op. case _ => - E(s"Unexpected parameter type for @NCIntentTerm annotated argument [mdlId=$id, origin=$origin, type=${pGenType.getTypeName}, arg=${mkArg()}]") + // Scala. For methods which added as + if COMP_CLS.exists(_ == paramGenType) then + if (!warned) + warned = true // TODO: text + logger.warn(s"Method arguments types cannot be detected and checked: ${method2Str(mtd)}") + else + E(s"Unexpected parameter type for @NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, type=${paramGenType.getTypeName}, arg=${mkArg()}]") // Other types. else - E(s"Unexpected parameter type for @NCIntentTerm annotated argument [mdlId=$id, origin=$origin, type=${class2Str(pClass)}, arg=${mkArg()}]") + E(s"Unexpected parameter type for @NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin, type=${class2Str(argClass)}, arg=${mkArg()}]") } /** @@ -457,7 +493,7 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: def mkArg(): String = arg2Str(mtd, i, ctxFirstParam) val p1 = "its @NCIntentTerm annotated argument" - val p2 = s"[mdlId=$id, origin=$origin, arg=${mkArg()}]" + val p2 = s"[mdlId=$mdlId, origin=$origin, arg=${mkArg()}]" // Argument is single entity but defined as not single entity. if cls == CLS_ENTITY && (min != 1 || max != 1) then @@ -475,60 +511,61 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: /** * - * @param mo + * @param intentDecls + * @param intents + * @param method + * @param obj */ - private def processMethod(intentDecls: mutable.Buffer[NCIDLIntent], intents: mutable.Buffer[NCIntentData], mo: MethodOwner): Unit = - val m = mo.method - val mtdStr = method2Str(m) - lazy val samples = scanSamples(mo.method) + private def processMethod(intentDecls: mutable.Buffer[NCIDLIntent], intents: mutable.Buffer[NCIntentData], method: Method, obj: Object): Unit = + val mtdStr = method2Str(method) + lazy val samples = scanSamples(method) def bindIntent(intent: NCIDLIntent, cb: NCCallback): Unit = if intents.exists(i => i._1.id == intent.id && i._2.id != cb.id) then - E(s"The intent cannot be bound to more than one callback [mdlId=$id, origin=$origin, class=${mo.className}, intentId=${intent.id}]") + E(s"The intent cannot be bound to more than one callback [mdlId=$mdlId, origin=$origin, class=${getClassName(method.getDeclaringClass)}, intentId=${intent.id}]") else intentDecls += intent - intents += NCIntentData(intent, prepareCallback(mo, intent), samples.getOrElse(intent.id, Seq.empty)) + intents += NCIntentData(intent, cb, samples.getOrElse(intent.id, Seq.empty)) def existsForOtherMethod(id: String): Boolean = intents.find(_.intent.id == id) match - case Some(i) => i.callback.method != m + case Some(i) => i.callback.method != method case None => false // 1. Process inline intent declarations by @NCIntent annotation. - val annsIntents = m.getAnnotationsByType(CLS_INTENT) + val annsIntents = method.getAnnotationsByType(CLS_INTENT) checkSingle(annsIntents, (a:NCIntent) => a.value, mtdStr) for (ann <- annsIntents; intent <- NCIDLCompiler.compile(ann.value, cfg, mtdStr)) if intentDecls.exists(_.id == intent.id && existsForOtherMethod(intent.id)) then - E(s"Duplicate intent ID [mdlId=$id, origin=$origin, callback=$mtdStr, id=${intent.id}]") + E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin, callback=$mtdStr, id=${intent.id}]") else - bindIntent(intent, prepareCallback(mo, intent)) + bindIntent(intent, prepareCallback(method, obj, intent)) // 2. Process intent references from @NCIntentRef annotation. - val annRefs = m.getAnnotationsByType(CLS_INTENT_REF) + val annRefs = method.getAnnotationsByType(CLS_INTENT_REF) checkSingle(annRefs, (a:NCIntentRef) => a.value, mtdStr) for (ann <- annRefs) val refId = ann.value.trim intentDecls.find(_.id == refId) match - case Some(intent) => bindIntent(intent, prepareCallback(mo, intent)) - case None => E(s"@NCIntentRef(\"$refId\") references unknown intent ID [mdlId=$id, origin=$origin, refId=$refId, callback=$mtdStr]") + case Some(intent) => bindIntent(intent, prepareCallback(method, obj, intent)) + case None => E(s"@NCIntentRef(\"$refId\") references unknown intent ID [mdlId=$mdlId, origin=$origin, refId=$refId, callback=$mtdStr]") /** * - * @param mo + * @param method + * @param obj * @param intent * @return */ - private def prepareCallback(mo: MethodOwner, intent: NCIDLIntent): NCCallback = - val mtd = mo.method - + private def prepareCallback(method: Method, obj: Object, intent: NCIDLIntent): NCCallback = // Checks method result type. - if mtd.getReturnType != CLS_QRY_RES then - E(s"Unexpected result type for @NCIntent annotated method [mdlId=$id, intentId=${intent.id}, type=${class2Str(mtd.getReturnType)}, callback=${method2Str(mtd)}]") + if method.getReturnType != CLS_QRY_RES then + E(s"Unexpected result type for @NCIntent annotated method [mdlId=$mdlId, intentId=${intent.id}, type=${class2Str(method.getReturnType)}, callback=${method2Str(method)}]") - val allParamTypes = mtd.getParameterTypes.toSeq + val allParamTypes = method.getParameterTypes.toSeq val ctxFirstParam = allParamTypes.nonEmpty && allParamTypes.head == CLS_INTENT_MATCH def getSeq[T](data: Seq[T]): Seq[T] = @@ -536,18 +573,18 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: else if ctxFirstParam then data.drop(1) else data - val allAnns = mtd.getParameterAnnotations + val allAnns = method.getParameterAnnotations val tokParamAnns = getSeq(allAnns.toIndexedSeq).filter(_ != null) val tokParamTypes = getSeq(allParamTypes) // Checks entities parameters annotations count. if tokParamAnns.sizeIs != tokParamTypes.length then - E(s"Unexpected annotations count for @NCIntent annotated method [mdlId=$id, intentId=${intent.id}, count=${tokParamAnns.size}, callback=${method2Str(mtd)}]") + E(s"Unexpected annotations count for @NCIntent annotated method [mdlId=$mdlId, intentId=${intent.id}, count=${tokParamAnns.size}, callback=${method2Str(method)}]") // Gets terms IDs. val termIds = tokParamAnns.toList.zipWithIndex.map { case (annArr, idx) => - def mkArg(): String = arg2Str(mtd, idx, ctxFirstParam) + def mkArg(): String = arg2Str(method, idx, ctxFirstParam) val termAnns = annArr.filter(_.isInstanceOf[NCIntentTerm]) @@ -556,15 +593,15 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: case 1 => termAnns.head.asInstanceOf[NCIntentTerm].value case 0 => if idx == 0 then - E(s"Missing @NCIntentTerm annotation or wrong type of the 1st parameter (must be 'NCIntentMatch') for [mdlId=$id, intentId=${intent.id}, arg=${mkArg()}]") + E(s"Missing @NCIntentTerm annotation or wrong type of the 1st parameter (must be 'NCIntentMatch') for [mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]") else - E(s"Missing @NCIntentTerm annotation for [mdlId=$id, intentId=${intent.id}, arg=${mkArg()}]") + E(s"Missing @NCIntentTerm annotation for [mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]") - case _ => E(s"Too many @NCIntentTerm annotations for [mdlId=$id, intentId=${intent.id}, arg=${mkArg()}]") + case _ => E(s"Too many @NCIntentTerm annotations for [mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]") } if NCUtils.containsDups(termIds) then - E(s"Duplicate term IDs in @NCIntentTerm annotations [mdlId=$id, intentId=${intent.id}, dups=${NCUtils.getDups(termIds).mkString(", ")}, callback=${method2Str(mtd)}]") + E(s"Duplicate term IDs in @NCIntentTerm annotations [mdlId=$mdlId, intentId=${intent.id}, dups=${NCUtils.getDups(termIds).mkString(", ")}, callback=${method2Str(method)}]") val terms = intent.terms @@ -575,27 +612,25 @@ class NCAnnotationsScanner(mdl: NCModel) extends LazyLogging: if invalidIds.nonEmpty then // Report only the first one for simplicity & clarity. - E(s"Unknown term ID in @NCIntentTerm annotation [mdlId=$id, intentId=${intent.id}, termId=${invalidIds.head}, callback=${method2Str(mtd)}]") - - val paramGenTypes = getSeq(mtd.getGenericParameterTypes.toIndexedSeq) + E(s"Unknown term ID in @NCIntentTerm annotation [mdlId=$mdlId, intentId=${intent.id}, termId=${invalidIds.head}, callback=${method2Str(method)}]") - require(tokParamTypes.sizeIs == paramGenTypes.length) + val paramGenTypes = getSeq(method.getGenericParameterTypes.toIndexedSeq) // Checks parameters. - checkTypes(mtd, tokParamTypes, paramGenTypes, ctxFirstParam) + checkTypes(method, tokParamTypes, paramGenTypes, ctxFirstParam) // Checks limits. val allLimits = terms.map(t => t.id.orNull -> (t.min, t.max)).toMap - checkMinMax(mtd, tokParamTypes, termIds.map(allLimits), ctxFirstParam) + checkMinMax(method, tokParamTypes, termIds.map(allLimits), ctxFirstParam) - NCCallback(mtd, + NCCallback(method, (ctx: NCIntentMatch) => val args = mutable.Buffer.empty[AnyRef] if ctxFirstParam then args += ctx - args ++= prepareParams(id, mtd, tokParamTypes, termIds.map(ctx.getTermEntities), ctxFirstParam) + args ++= prepareParams(mdlId, method, tokParamTypes, termIds.map(ctx.getTermEntities), ctxFirstParam) - invoke(id, mo, args.toArray) + invoke(mdlId, method, obj, args.toArray) ) /** diff --git a/nlpcraft/src/test/resources/scan/idl.idl b/nlpcraft/src/test/resources/scan/idl.idl index 674cb86..24fa7ff 100644 --- a/nlpcraft/src/test/resources/scan/idl.idl +++ b/nlpcraft/src/test/resources/scan/idl.idl @@ -15,4 +15,4 @@ * limitations under the License. */ -intent=intent2 term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] term(opt)~{# == 'id3'}? \ No newline at end of file +intent=impIntId term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] term(opt)~{# == 'id3'}? \ No newline at end of file diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelScala.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelScala.scala deleted file mode 100644 index a01f6c2..0000000 --- a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelScala.scala +++ /dev/null @@ -1,67 +0,0 @@ -package org.apache.nlpcraft.internal.impl - -import org.apache.nlpcraft.* -import org.apache.nlpcraft.nlp.util.opennlp.* -import org.junit.jupiter.api.Test - -object NCTestModelScala: - @NCIntentImport(Array("scan/idl.idl")) - object NCTestModelScalaObj extends NCModel: - override def getConfig: NCModelConfig = CFG - override def getPipeline: NCModelPipeline = EN_PIPELINE - - @NCIntent("intent=intent1 term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] term(opt)~{# == 'id3'}?") - @NCIntentSample(Array("What are the least performing categories for the last quarter?")) - def intent1( - @NCIntentTerm("single") single: NCEntity, - @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq. - @NCIntentTerm("opt") opt: Option[NCEntity] - ): NCResult = new NCResult() - - @NCIntentRef("intent2") - @NCIntentSampleRef("scan/samples.txt") - def intent2( - @NCIntentTerm("single") single: NCEntity, - @NCIntentTerm("list") list: List[NCEntity], // scala List. - @NCIntentTerm("opt") opt: Option[NCEntity] - ): NCResult = new NCResult() - - @NCIntentImport(Array("scan/idl.idl")) - class NCTestModelScalaClass extends NCModel: - override def getConfig: NCModelConfig = CFG - override def getPipeline: NCModelPipeline = EN_PIPELINE - - @NCIntent("intent=intent1 term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] term(opt)~{# == 'id3'}?") - @NCIntentSample(Array("What are the least performing categories for the last quarter?")) - def intent1( - @NCIntentTerm("single") single: NCEntity, - @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq. - @NCIntentTerm("opt") opt: Option[NCEntity] - ) = new NCResult() - - @NCIntentRef("intent2") - @NCIntentSampleRef("scan/samples.txt") - def intent2( - @NCIntentTerm("single") single: NCEntity, - @NCIntentTerm("list") list: List[NCEntity], // scala List. - @NCIntentTerm("opt") opt: Option[NCEntity] - ) = new NCResult() - - def mkModel: NCModel = - new NCModelAdapter(CFG, EN_PIPELINE): - @NCIntentImport(Array("scan/idl.idl")) - @NCIntent("intent=intent1 term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] term(opt)~{# == 'id3'}?") - @NCIntentSample(Array("What are the least performing categories for the last quarter?")) - def intent1( - @NCIntentTerm("single") single: NCEntity, - @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq. - @NCIntentTerm("opt") opt: Option[NCEntity] - ): NCResult = new NCResult() - - @NCIntentRef("intent2") - @NCIntentSampleRef("scan/samples.txt") - def intent2( - @NCIntentTerm("single") single: NCEntity, - @NCIntentTerm("list") list: List[NCEntity], // scala List. - @NCIntentTerm("opt") opt: Option[NCEntity] - ): NCResult = new NCResult() \ No newline at end of file diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationInvalidArgsSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationInvalidArgsSpec.scala new file mode 100644 index 0000000..1314dd0 --- /dev/null +++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationInvalidArgsSpec.scala @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nlpcraft.internal.impl.scan + +import org.apache.nlpcraft.* +import org.apache.nlpcraft.internal.impl.NCAnnotationsScanner +import org.apache.nlpcraft.nlp.util.* +import org.apache.nlpcraft.nlp.util.opennlp.* +import org.junit.jupiter.api.Test + +import java.util + +/** + * It tests invalid intents methods parameters types usage. + * Note that for some kind of models (it depends on creation type) we can't check methods arguments during scan. + */ +class NCAnnotationInvalidArgsSpec: + class DefinedClassModelValid extends NCModel: + override def getConfig: NCModelConfig = CFG + override def getPipeline: NCModelPipeline = EN_PIPELINE + + // Valid parameters. + @NCIntent("intent=validList term(list)~{# == 'x'}[0,10]") + def validList(@NCIntentTerm("list") list: List[NCEntity]): NCResult = processListEntity(list) + + @NCIntent("intent=validOpt term(opt)~{# == 'x'}?") + def validOpt(@NCIntentTerm("opt") opt: Option[NCEntity]): NCResult = processOptEntity(opt) + + class DefinedClassModelInvalidLst extends NCModel: + override def getConfig: NCModelConfig = CFG + override def getPipeline: NCModelPipeline = EN_PIPELINE + + // Invalid parameters. + @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]") + def invalidList(@NCIntentTerm("list") list: List[Int]): NCResult = processListInt(list) + + class DefinedClassModelInvalidOpt extends NCModel: + override def getConfig: NCModelConfig = CFG + override def getPipeline: NCModelPipeline = EN_PIPELINE + + // Invalid parameters. + @NCIntent("intent=invalidOpt term(opt)~{# == 'x'}?") + def invalidOpt(@NCIntentTerm("opt") opt: Option[Int]): NCResult = processOptInt(opt) + + + private val CHECKED_MDL_VALID: NCModel = new DefinedClassModelValid + private val CHECKED_MDL_INVALID_LST: NCModel = new DefinedClassModelInvalidLst + private val CHECKED_MDL_INVALID_OPT: NCModel = new DefinedClassModelInvalidOpt + private val UNCHECKED_MDL_MIX: NCModel = + new NCModel: + override def getConfig: NCModelConfig = CFG + override def getPipeline: NCModelPipeline = EN_PIPELINE + + // Valid parameters. + @NCIntent("intent=validList term(list)~{# == 'x'}[0,10]") + def validList(@NCIntentTerm("list") list: List[NCEntity]): NCResult = processListEntity(list) + + @NCIntent("intent=validOpt term(opt)~{# == 'x'}?") + def validOpt(@NCIntentTerm("opt") opt: Option[NCEntity]): NCResult = processOptEntity(opt) + + // Invalid parameters. + @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]") + def invalidList(@NCIntentTerm("list") list: List[Int]): NCResult = processListInt(list) + + @NCIntent("intent=invalidOpt term(opt)~{# == 'x'}?") + def invalidOpt(@NCIntentTerm("opt") opt: Option[Int]): NCResult = processOptInt(opt) + + private val INTENT_MATCH = + val ent = NCTestEntity("id", "reqId", tokens = NCTestToken()) + + new NCIntentMatch: + override def getIntentId: String = "impIntId" + override def getIntentEntities: util.List[util.List[NCEntity]] = col(col(ent)) + override def getTermEntities(idx: Int): util.List[NCEntity] = col(ent) + override def getTermEntities(termId: String): util.List[NCEntity] = col(ent) + override def getVariant: NCVariant = new NCVariant: + override def getEntities: util.List[NCEntity] = col(ent) + + private def mkResult0(obj: Any): NCResult = + println(s"Result body: $obj, class=${obj.getClass}") + val res = new NCResult() + res.setBody(obj) + res + + private def processOptInt(opt: Option[Int]): NCResult = + // Access and cast. + val body: Int = opt.get + mkResult0(body) + + private def processOptEntity(opt: Option[NCEntity]): NCResult = + // Access and cast. + val body: NCEntity = opt.get + mkResult0(body) + + private def processListInt(list: List[Int]): NCResult = + // Access and cast. + val bodyHead: Int = list.head + mkResult0(list) + + private def processListEntity(list: List[NCEntity]): NCResult = + // Access and cast. + val bodyHead: NCEntity = list.head + mkResult0(list) + + private def col[T](t: T): util.List[T] = java.util.Collections.singletonList(t) + + private def testOk(mdl: NCModel, intentId: String): Unit = + val cb = new NCAnnotationsScanner(mdl).scan().find(_.intent.id == intentId).get.callback + + println(s"Test finished [modelClass=${mdl.getClass}, intent=$intentId, result=${cb.cbFun.apply(INTENT_MATCH)}") + + private def testRuntimeClassCast(mdl: NCModel, intentId: String): Unit = + val cb = new NCAnnotationsScanner(mdl).scan().find(_.intent.id == intentId).get.callback + + try + cb.cbFun.apply(INTENT_MATCH) + + require(false) + catch + case e: NCException => + if e.getCause != null && e.getCause.isInstanceOf[ClassCastException] then + println(s"Expected error: $e") + e.printStackTrace(System.out) + else throw e + + private def testScanError(mdl: NCModel, intentId: String): Unit = + try + new NCAnnotationsScanner(mdl) + catch + case e: NCException => + println(s"Expected error: $e") + e.printStackTrace(System.out) + + @Test + def test(): Unit = + testOk(CHECKED_MDL_VALID, "validList") + testOk(CHECKED_MDL_VALID, "validOpt") + testScanError(CHECKED_MDL_INVALID_LST, "invalidList") + testScanError(CHECKED_MDL_INVALID_OPT, "invalidOpt") + + testOk(UNCHECKED_MDL_MIX, "validList") + testOk(UNCHECKED_MDL_MIX, "validOpt") + testRuntimeClassCast(UNCHECKED_MDL_MIX, "invalidList") + testRuntimeClassCast(UNCHECKED_MDL_MIX, "invalidOpt") \ No newline at end of file diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationNestedSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationNestedSpec.scala new file mode 100644 index 0000000..003dbb7 --- /dev/null +++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationNestedSpec.scala @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nlpcraft.internal.impl.scan + +import org.apache.nlpcraft.* +import org.apache.nlpcraft.internal.impl.NCAnnotationsScanner +import org.apache.nlpcraft.nlp.util.opennlp.* +import org.junit.jupiter.api.Test + +/** + * It tests imports and nested objects usage. + */ +class NCAnnotationNestedSpec: + private val mdl: NCModel = new NCModel : + override def getConfig: NCModelConfig = CFG + override def getPipeline: NCModelPipeline = EN_PIPELINE + + @NCIntentObject + val nested: Object = new Object() { + @NCIntentObject + val nested2: Object = new Object() { + @NCIntentImport(Array("scan/idl.idl")) + @NCIntent("intent=intent3 term(x)~{true}") + def intent1(@NCIntentTerm("x") x: NCEntity) = new NCResult() + } + + @NCIntent("intent=intent2 term(x)~{true}") + def intent1(@NCIntentTerm("x") x: NCEntity) = new NCResult() + } + + @NCIntent("intent=intent1 term(x)~{true}") + def intent1(@NCIntentTerm("x") x: NCEntity) = new NCResult() + + // Imported in `nested2` (scan/idl.idl) + @NCIntentRef("impIntId") + def intent4( + @NCIntentTerm("single") single: NCEntity, + @NCIntentTerm("list") list: List[NCEntity], + @NCIntentTerm("opt") opt: Option[NCEntity] + ): NCResult = new NCResult() + + @Test + def test(): Unit = require(new NCAnnotationsScanner(mdl).scan().sizeIs == 4) diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScannerSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationSpec.scala similarity index 82% rename from nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScannerSpec.scala rename to nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationSpec.scala index 0e75a7c..2e8f541 100644 --- a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCAnnotationsScannerSpec.scala +++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCAnnotationSpec.scala @@ -15,16 +15,17 @@ * limitations under the License. */ -package org.apache.nlpcraft.internal.impl +package org.apache.nlpcraft.internal.impl.scan import org.apache.nlpcraft.NCModel +import org.apache.nlpcraft.internal.impl.* import org.junit.jupiter.api.Test /** - * + * It tests various ways created models scanning. */ -class NCAnnotationsScannerSpec: - // Different ways for preparing model instances. +class NCAnnotationSpec: + // Different ways preparing model instances. private val mdls = Seq( NCTestModelJava.mkModel, NCTestModelScala.mkModel, @@ -35,6 +36,6 @@ class NCAnnotationsScannerSpec: @Test def test(): Unit = for (mdl <- mdls) + println(s"Model=${mdl.getClass}") val data = new NCAnnotationsScanner(mdl).scan() - println(s"Data=$data") diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelJava.java b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java similarity index 88% rename from nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelJava.java rename to nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java index bc40b2c..c407748 100644 --- a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCTestModelJava.java +++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelJava.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.nlpcraft.internal.impl; +package org.apache.nlpcraft.internal.impl.scan; import org.apache.nlpcraft.NCIntent; import org.apache.nlpcraft.NCIntentImport; @@ -36,15 +36,19 @@ import java.util.Optional; * */ public class NCTestModelJava { + /** + * + * @return + */ public static NCModel mkModel() { return new NCModelAdapter(NCTestConfigJava.CFG, NCTestConfigJava.EN_PIPELINE) { @NCIntentImport({"scan/idl.idl"}) @NCIntent( - "intent=intent1 term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] term(opt)~{# == 'id3'}?" + "intent=locInt term(single)~{# == 'id1'} term(list)~{# == 'id2'}[0,10] term(opt)~{# == 'id3'}?" ) @NCIntentSample("What are the least performing categories for the last quarter?") - NCResult intent1( + NCResult intent( @NCIntentTerm("single") NCEntity single, @NCIntentTerm("list") List<NCEntity> list, @NCIntentTerm("opt") Optional<NCEntity> opt @@ -52,9 +56,9 @@ public class NCTestModelJava { return new NCResult(); } - @NCIntentRef("intent2") + @NCIntentRef("impIntId") @NCIntentSampleRef("scan/samples.txt") - NCResult intent2( + NCResult intentImport( @NCIntentTerm("single") NCEntity single, @NCIntentTerm("list") List<NCEntity> list, @NCIntentTerm("opt") Optional<NCEntity> opt 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 new file mode 100644 index 0000000..fe691b1 --- /dev/null +++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nlpcraft.internal.impl.scan + +import org.apache.nlpcraft.* +import org.apache.nlpcraft.nlp.util.opennlp.* + +/** + * + */ +object NCTestModelScala: + @NCIntentImport(Array("scan/idl.idl")) + object NCTestModelScalaObj extends NCModel : + override def getConfig: NCModelConfig = CFG + override def getPipeline: NCModelPipeline = EN_PIPELINE + + @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( + @NCIntentTerm("single") single: NCEntity, + @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq. + @NCIntentTerm("opt") opt: Option[NCEntity] + ): NCResult = new NCResult() + + @NCIntentRef("impIntId") + @NCIntentSampleRef("scan/samples.txt") + def intentImport( + @NCIntentTerm("single") single: NCEntity, + @NCIntentTerm("list") list: List[NCEntity], // scala List. + @NCIntentTerm("opt") opt: Option[NCEntity] + ): NCResult = new NCResult() + + @NCIntentImport(Array("scan/idl.idl")) class NCTestModelScalaClass extends NCModel : + override def getConfig: NCModelConfig = CFG + override def getPipeline: NCModelPipeline = EN_PIPELINE + + @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( + @NCIntentTerm("single") single: NCEntity, + @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq. + @NCIntentTerm("opt") opt: Option[NCEntity] + ) = new NCResult() + + @NCIntentRef("impIntId") + @NCIntentSampleRef("scan/samples.txt") + def intentImport( + @NCIntentTerm("single") single: NCEntity, + @NCIntentTerm("list") list: List[NCEntity], // scala List. + @NCIntentTerm("opt") opt: Option[NCEntity] + ) = new NCResult() + + /** + * + * @return + */ + def mkModel: NCModel = new NCModelAdapter(CFG, EN_PIPELINE) : + @NCIntentImport(Array("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( + @NCIntentTerm("single") single: NCEntity, + @NCIntentTerm("list") list: Seq[NCEntity], // scala Seq. + @NCIntentTerm("opt") opt: Option[NCEntity] + ): NCResult = new NCResult() + + @NCIntentRef("impIntId") + @NCIntentSampleRef("scan/samples.txt") + def intentImport( + @NCIntentTerm("single") single: NCEntity, + @NCIntentTerm("list") list: List[NCEntity], // scala List. + @NCIntentTerm("opt") opt: Option[NCEntity] + ): NCResult = new NCResult() diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala index a1c1045..2df2308 100644 --- a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala +++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIDLFunctions.scala @@ -21,7 +21,7 @@ import org.apache.nlpcraft.* import org.apache.nlpcraft.internal.intent.* import org.apache.nlpcraft.internal.intent.compiler.* import org.apache.nlpcraft.internal.intent.compiler.functions.* -import org.apache.nlpcraft.nlp.util.{NCTestRequest, NCTestToken} +import org.apache.nlpcraft.nlp.util.* import org.apache.nlpcraft.nlp.util.opennlp.* import org.junit.jupiter.api.BeforeEach @@ -140,7 +140,7 @@ private[functions] object NCIDLFunctions: /** * * @param id - * @param srvReqId + * @param reqId * @param value * @param groups * @param meta @@ -149,21 +149,15 @@ private[functions] object NCIDLFunctions: */ def mkEntity( id: String = UUID.randomUUID().toString, - srvReqId: String = UUID.randomUUID().toString, - value: String = null, + reqId: String = UUID.randomUUID().toString, + value: String = null, // TODO: add tests for usage. groups: Set[String] = null, meta: Map[String, AnyRef] = Map.empty[String, AnyRef], tokens: NCTestToken* ): NCEntity = require(tokens.nonEmpty) - new NCPropertyMapAdapter with NCEntity(): - meta.foreach { (k, v) => put(k, v) } - - override def getTokens: java.util.List[NCToken] = tokens.asJava - override def getRequestId: String = srvReqId - override def getGroups: util.Set[String] = if groups != null then groups.asJava else util.Collections.singleton(id) - override def getId: String = id + NCTestEntity(id, reqId, groups, meta, tokens*) import org.apache.nlpcraft.internal.intent.compiler.functions.NCIDLFunctions.* diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestEntity.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestEntity.scala new file mode 100644 index 0000000..0640a80 --- /dev/null +++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestEntity.scala @@ -0,0 +1,47 @@ +/* + * 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.nlp.util + +import org.apache.nlpcraft.* +import org.apache.nlpcraft.nlp.util.NCTestPipeline.* +import scala.jdk.CollectionConverters.* +import java.util.Set as JSet +import java.util.List as JList + +/** + * Entity test implementation. + * + * @param id + * @param reqId + * @param groups + * @param meta + * @param tokens + */ +case class NCTestEntity( + id: String, + reqId: String = null, + groups: Set[String] = null, + meta: Map[String, AnyRef] = null, + tokens: NCTestToken* +) extends NCPropertyMapAdapter with NCEntity: + if meta != null then meta.foreach { (k, v) => put(k, v) } + + override def getTokens: JList[NCToken] = tokens.asJava + override def getRequestId: String = reqId + override def getGroups: JSet[String] = (if groups != null then groups else Set(id)).asJava + override def getId: String = id \ No newline at end of file