• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TouK / nussknacker / 5976637142

25 Aug 2023 01:43PM UTC coverage: 81.47% (+0.03%) from 81.438%
5976637142

push

github

Filemon279
Fix migration in 1.11

25 of 25 new or added lines in 2 files covered. (100.0%)

14865 of 18246 relevant lines covered (81.47%)

5.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.23
/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/DefinitionExtractor.scala
1
package pl.touk.nussknacker.engine.definition
2

3
import com.typesafe.scalalogging.LazyLogging
4
import pl.touk.nussknacker.engine.api.MethodToInvoke
5
import pl.touk.nussknacker.engine.api.component.SingleComponentConfig
6
import pl.touk.nussknacker.engine.api.context.transformation._
7
import pl.touk.nussknacker.engine.api.definition.{OutputVariableNameDependency, Parameter, TypedNodeDependency, WithExplicitTypesToExtract}
8
import pl.touk.nussknacker.engine.api.process.{ClassExtractionSettings, WithCategories}
9
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult, Unknown}
10
import pl.touk.nussknacker.engine.definition.DefinitionExtractor._
11
import pl.touk.nussknacker.engine.definition.MethodDefinitionExtractor.MethodDefinition
12
import pl.touk.nussknacker.engine.types.TypesInformationExtractor
13

14
import java.lang.reflect.Method
15
import scala.runtime.BoxedUnit
16

17
class DefinitionExtractor[T](methodDefinitionExtractor: MethodDefinitionExtractor[T]) {
18

19
  def extract(objWithCategories: WithCategories[T], mergedComponentConfig: SingleComponentConfig): ObjectWithMethodDef = {
20
    val obj = objWithCategories.value
32✔
21

22
    def fromMethodDefinition(methodDef: MethodDefinition): StandardObjectWithMethodDef = {
23
      // TODO: Use ContextTransformation API to check if custom node is adding some output variable
24
      def notReturnAnything(typ: TypingResult) = Set[TypingResult](Typed[Void], Typed[Unit], Typed[BoxedUnit]).contains(typ)
32✔
25
      val objectDefinition = ObjectDefinition(
32✔
26
        methodDef.definedParameters,
32✔
27
        Option(methodDef.returnType).filterNot(notReturnAnything),
32✔
28
        objWithCategories.categories,
32✔
29
        mergedComponentConfig)
30
      val implementationInvoker = new MethodBasedComponentImplementationInvoker(obj, methodDef)
32✔
31
      StandardObjectWithMethodDef(implementationInvoker, obj, objectDefinition, methodDef.runtimeClass)
32✔
32
    }
33

34
    (obj match {
35
      case e: GenericNodeTransformation[_] =>
36
        Right(GenericNodeTransformationMethodDef(e, objWithCategories.categories, mergedComponentConfig))
26✔
37
      case _ =>
38
        methodDefinitionExtractor.extractMethodDefinition(obj, findMethodToInvoke(obj), mergedComponentConfig).map(fromMethodDefinition)
32✔
39
    }).fold(msg => throw new IllegalArgumentException(msg), identity)
32✔
40

41
  }
42

43
  private def findMethodToInvoke(obj: Any): Method = {
44
    val methodsToInvoke = obj.getClass.getMethods.toList.filter { m =>
32✔
45
      m.getAnnotation(classOf[MethodToInvoke]) != null
32✔
46
    }
47
    methodsToInvoke match {
48
      case Nil =>
49
        throw new IllegalArgumentException(s"Missing method to invoke for object: " + obj)
×
50
      case head :: Nil =>
51
        head
32✔
52
      case moreThanOne =>
53
        throw new IllegalArgumentException(s"More than one method to invoke: " + moreThanOne + " in object: " + obj)
×
54
    }
55
  }
56

57
}
58

59
object DefinitionExtractor {
60

61
  case class ObjectWithType(obj: Any, typ: TypingResult)
62

63
  // TODO: rename to ComponentDefinitionWithImplementation
64
  sealed trait ObjectWithMethodDef {
65

66
    // TODO: It should be exposed only for components - not for global variables
67
    def implementationInvoker: ComponentImplementationInvoker
68

69
    // For purpose of transforming (e.g.) stubbing of the implementation
70
    def withImplementationInvoker(implementationInvoker: ComponentImplementationInvoker): ObjectWithMethodDef
71

72
    def obj: Any
73

74
    // TODO: it should be available only for StandardObjectWithMethodDef
75
    def returnType: Option[TypingResult]
76

77
    protected[definition] def categories: Option[List[String]]
78

79
    def availableForCategory(category: String): Boolean = categories.isEmpty || categories.exists(_.contains(category))
2✔
80

81
    def componentConfig: SingleComponentConfig
82

83
  }
84

85
  trait ComponentImplementationInvoker extends Serializable {
86

87
    def invokeMethod(params: Map[String, Any],
88
                     outputVariableNameOpt: Option[String],
89
                     additional: Seq[AnyRef]): Any
90

91
  }
92

93
  object ComponentImplementationInvoker {
94

95
    val nullImplementationInvoker: ComponentImplementationInvoker = new ComponentImplementationInvoker {
10✔
96
      override def invokeMethod(params: Map[String, Any],
97
                                outputVariableNameOpt: Option[String],
98
                                additional: Seq[AnyRef]): Any = null
4✔
99
    }
100

101
  }
102

103

104
  case class GenericNodeTransformationMethodDef(override val implementationInvoker: ComponentImplementationInvoker,
105
                                                obj: GenericNodeTransformation[_],
106
                                                override protected[definition] val categories: Option[List[String]],
107
                                                override val componentConfig: SingleComponentConfig) extends ObjectWithMethodDef {
108
    override def withImplementationInvoker(implementationInvoker: ComponentImplementationInvoker): ObjectWithMethodDef =
109
      copy(implementationInvoker = implementationInvoker)
6✔
110

111
    def returnType: Option[TypingResult] = if (obj.nodeDependencies.contains(OutputVariableNameDependency)) Some(Unknown) else None
24✔
112

113
  }
114

115
  object GenericNodeTransformationMethodDef {
116

117
    def apply(obj: GenericNodeTransformation[_],
118
              categories: Option[List[String]],
119
              componentConfig: SingleComponentConfig): GenericNodeTransformationMethodDef = {
120
      val implementationInvoker = new ComponentImplementationInvoker {
26✔
121
        override def invokeMethod(params: Map[String, Any], outputVariableNameOpt: Option[String], additional: Seq[AnyRef]): Any = {
122
          val additionalParams = obj.nodeDependencies.map {
28✔
123
            case TypedNodeDependency(klazz) =>
124
              additional.find(klazz.isInstance).map(TypedNodeDependencyValue)
26✔
125
                .getOrElse(throw new IllegalArgumentException(s"Failed to find dependency: $klazz"))
26✔
126
            case OutputVariableNameDependency => outputVariableNameOpt.map(OutputVariableNameValue).getOrElse(throw new IllegalArgumentException("Output variable not defined"))
20✔
127
            case other => throw new IllegalArgumentException(s"Cannot handle dependency $other")
×
128
          }
129
          val finalStateValue = additional.collectFirst {
28✔
130
            case FinalStateValue(value) => value
28✔
131
          }.getOrElse(throw new IllegalArgumentException("Final state not passed to invokeMethod"))
×
132
          //we assume parameters were already validated!
133
          obj.implementation(params, additionalParams, finalStateValue.asInstanceOf[Option[obj.State]])
28✔
134
        }
135
      }
136
      new GenericNodeTransformationMethodDef(implementationInvoker, obj, categories, componentConfig)
26✔
137
    }
138

139
  }
140

141

142
  case class FinalStateValue(value: Option[Any])
143

144
  // TOOD: rename to MethodBasedComponentWithImplementation
145
  case class StandardObjectWithMethodDef(implementationInvoker: ComponentImplementationInvoker,
146
                                         obj: Any,
147
                                         objectDefinition: ObjectDefinition,
148
                                         // TODO: it should be removed - instead implementationInvoker should be transformed
149
                                         runtimeClass: Class[_]) extends ObjectWithMethodDef {
150
    override def withImplementationInvoker(implementationInvoker: ComponentImplementationInvoker): ObjectWithMethodDef =
151
      copy(implementationInvoker = implementationInvoker)
8✔
152

153
    def parameters: List[Parameter] = objectDefinition.parameters
32✔
154

155
    override def returnType: Option[TypingResult] = objectDefinition.returnType
32✔
156

157
    override protected[definition] def categories: Option[List[String]] = objectDefinition.categories
2✔
158

159
    override def componentConfig: SingleComponentConfig = objectDefinition.componentConfig
×
160

161
  }
162

163
  private[definition] class MethodBasedComponentImplementationInvoker(obj: Any, private[definition] val methodDef: MethodDefinition)
164
    extends ComponentImplementationInvoker with LazyLogging {
165

166
    override def invokeMethod(params: Map[String, Any], outputVariableNameOpt: Option[String], additional: Seq[AnyRef]): Any = {
167
      methodDef.invoke(obj, params, outputVariableNameOpt, additional)
28✔
168
    }
169
  }
170

171
  // TODO: rename to ComponentStaticDefinition
172
  case class ObjectDefinition(parameters: List[Parameter],
173
                              returnType: Option[TypingResult],
174
                              categories: Option[List[String]],
175
                              componentConfig: SingleComponentConfig)
176

177
  object ObjectWithMethodDef {
178

179
    import cats.syntax.semigroup._
180

181
    def forMap[T](objs: Map[String, WithCategories[_ <: T]], methodExtractor: MethodDefinitionExtractor[T], externalConfig: Map[String, SingleComponentConfig]): Map[String, ObjectWithMethodDef] = {
182
      objs.map { case (id, obj) =>
16✔
183
        val config = externalConfig.getOrElse(id, SingleComponentConfig.zero) |+| obj.componentConfig
32✔
184
        id -> (obj, config)
32✔
185
      }.collect {
32✔
186
        case (id, (obj, config)) if !config.disabled =>
32✔
187
          id -> new DefinitionExtractor(methodExtractor).extract(obj, config)
32✔
188
      }
189
    }
190

191
    def withEmptyConfig[T](obj: T, methodExtractor: MethodDefinitionExtractor[T]): ObjectWithMethodDef = {
192
      new DefinitionExtractor(methodExtractor).extract(WithCategories(obj), SingleComponentConfig.zero)
2✔
193
    }
194
  }
195

196
  object TypesInformation {
197
    def extract(objectToExtractClassesFrom: Iterable[ObjectWithMethodDef])
198
               (implicit settings: ClassExtractionSettings): Set[TypeInfos.ClazzDefinition] = {
199
      val classesToExtractDefinitions = objectToExtractClassesFrom.flatMap(extractTypesFromObjectDefinition)
32✔
200
      TypesInformationExtractor.clazzAndItsChildrenDefinition(classesToExtractDefinitions)
32✔
201
    }
202

203
    def extractFromClassList(objectToExtractClassesFromCollection: Iterable[Class[_]])
204
                            (implicit settings: ClassExtractionSettings): Set[TypeInfos.ClazzDefinition] = {
205
      val ref = objectToExtractClassesFromCollection.map(Typed.apply)
34✔
206
      TypesInformationExtractor.clazzAndItsChildrenDefinition(ref)
34✔
207
    }
208

209
    private def extractTypesFromObjectDefinition(obj: ObjectWithMethodDef): List[TypingResult] = {
210
      def typesFromParameter(parameter: Parameter): List[TypingResult] = {
211
        val fromAdditionalVars = parameter.additionalVariables.values.map(_.typingResult)
30✔
212
        fromAdditionalVars.toList :+ parameter.typ
30✔
213
      }
214

215
      def typesFromParameters(obj: ObjectWithMethodDef): List[TypingResult] = {
216
        obj match {
217
          case static: StandardObjectWithMethodDef => static.parameters.flatMap(typesFromParameter)
31✔
218
          // WithExplicitTypesToExtract trait should be used in that case
219
          case _: GenericNodeTransformationMethodDef => List.empty
26✔
220
        }
221
      }
222

223
      def explicitTypes(obj: ObjectWithMethodDef): List[TypingResult] = {
224
        obj.obj match {
32✔
225
          case explicit: WithExplicitTypesToExtract => explicit.typesToExtract
26✔
226
          case _ => Nil
32✔
227
        }
228
      }
229

230
      obj.returnType.toList ::: typesFromParameters(obj) ::: explicitTypes(obj)
32✔
231
    }
232
  }
233

234
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc