Skip to content

Commit

Permalink
[NU-1836] Move converters from CONV util to extension methods. Implem…
Browse files Browse the repository at this point in the history
…ent generic conversion extension methods: is, to and toOrNull. (#7063)
  • Loading branch information
lukasz-bigorajski authored Oct 30, 2024
1 parent 06bad58 commit 333afd6
Show file tree
Hide file tree
Showing 30 changed files with 5,942 additions and 1,343 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package pl.touk.nussknacker.engine.api.generics

import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypedClass, TypingResult}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypedClass, TypingResult, Unknown}

object MethodTypeInfo {
private val arrayClass = classOf[Array[Object]]
Expand All @@ -22,6 +22,12 @@ object MethodTypeInfo {
def withoutVarargs(params: List[Parameter], result: TypingResult): MethodTypeInfo =
MethodTypeInfo(params, None, result)

def noArgTypeInfo(returnType: TypingResult) = MethodTypeInfo(
noVarArgs = Nil,
varArg = None,
result = returnType
)

}

case class MethodTypeInfo(noVarArgs: List[Parameter], varArg: Option[Parameter], result: TypingResult) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ object typing {
fields.map { case (fieldName, fieldType) =>
fieldName -> fieldType.withoutValue
},
runtimeObjType,
runtimeObjType.withoutValue,
additionalInfo
)

Expand Down Expand Up @@ -206,7 +206,7 @@ object typing {
case class TypedClass private[typing] (klass: Class[_], params: List[TypingResult]) extends SingleTypingResult {
override val valueOpt: None.type = None

override def withoutValue: TypedClass = this
override def withoutValue: TypedClass = TypedClass(klass, params.map(_.withoutValue))

override def display: String = {
val className = if (klass.isArray) "List" else ReflectUtils.simpleNameWithoutSuffix(runtimeObjType.klass)
Expand Down Expand Up @@ -353,8 +353,9 @@ object typing {
supertypeOfElementTypes(javaList.asScala.toList).withoutValue,
javaList
)
case set: java.util.Set[_] =>
genericTypeClass(classOf[java.util.Set[_]], List(supertypeOfElementTypes(set.asScala.toList)))
case typeFromInstance: TypedFromInstance => typeFromInstance.typingResult
// TODO: handle more types, for example Set
case other =>
Typed(other.getClass) match {
case typedClass: TypedClass =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class ExpressionSuggesterSpec
),
ClassDefinition(
Unknown,
Map("canCastTo" -> List(StaticMethodDefinition(MethodTypeInfo(Nil, None, Typed[Boolean]), "canCastTo", None))),
Map("is" -> List(StaticMethodDefinition(MethodTypeInfo(Nil, None, Typed[Boolean]), "is", None))),
Map.empty
),
)
Expand Down Expand Up @@ -775,22 +775,55 @@ class ExpressionSuggesterSpec

test("should suggest methods for unknown") {
spelSuggestionsFor("#unknown.") shouldBe List(
suggestion("canCastTo", Typed[Boolean]),
suggestion("is", Typed[Boolean]),
)
}

test("should suggest parameters for casts methods") {
spelSuggestionsFor("#unknown.canCastTo('')", column = 20) should contain theSameElementsAs List(
suggestion("String", Typed[String]),
test("should suggest parameters for casts/conversions methods on unknown") {
spelSuggestionsFor("#unknown.to('')", column = 13) should contain theSameElementsAs List(
suggestion("Duration", Typed[Duration]),
suggestion("LocalDateTime", Typed[LocalDateTime]),
suggestion("Map", Typed[java.util.Map[_, _]]),
suggestion("A", Typed[A]),
suggestion("AA", Typed[AA]),
suggestion("B", Typed[B]),
suggestion("C", Typed[C]),
suggestion("Util", Typed[Util]),
suggestion("WithList", Typed[WithList]),
suggestion("BigDecimal", Typed[java.math.BigDecimal]),
suggestion("BigInteger", Typed[java.math.BigInteger]),
suggestion("Boolean", Typed[java.lang.Boolean]),
suggestion("Double", Typed[java.lang.Double]),
suggestion("Float", Typed[java.lang.Float]),
suggestion("Long", Typed[java.lang.Long]),
suggestion("Integer", Typed[java.lang.Integer]),
suggestion("Short", Typed[java.lang.Short]),
suggestion("Byte", Typed[java.lang.Byte]),
suggestion("String", Typed[java.lang.String]),
suggestion("List", Typed.genericTypeClass[java.util.List[_]](List(Unknown))),
suggestion("Map", Typed.genericTypeClass[java.util.Map[_, _]](List(Unknown, Unknown))),
)
}

test("should suggest parameters for casts/conversions methods on string") {
spelSuggestionsFor("'11'.to('')", column = 9) should contain theSameElementsAs List(
suggestion("BigDecimal", Typed[java.math.BigDecimal]),
suggestion("BigInteger", Typed[java.math.BigInteger]),
suggestion("Boolean", Typed[java.lang.Boolean]),
suggestion("Double", Typed[java.lang.Double]),
suggestion("Float", Typed[java.lang.Float]),
suggestion("Long", Typed[java.lang.Long]),
suggestion("Integer", Typed[java.lang.Integer]),
suggestion("Short", Typed[java.lang.Short]),
suggestion("Byte", Typed[java.lang.Byte]),
)
}

test("should suggest parameters for casts/conversions methods on list to map") {
spelSuggestionsFor("{{key: 'a', value: 1}}.to('')", column = 27) should contain theSameElementsAs List(
suggestion(
"Map",
Typed.genericTypeClass[java.util.Map[_, _]](List(Typed.typedClass[String], Typed.typedClass[Integer]))
),
)
}

Expand Down Expand Up @@ -820,7 +853,7 @@ class ExpressionSuggesterSpec

val listMethodsSuggestion = suggestion(listSpelExpression)
val arrayMethodsSuggestion = suggestion(arraySpelExpression)
listMethodsSuggestion should contain theSameElementsAs arrayMethodsSuggestion
arrayMethodsSuggestion should contain allElementsOf listMethodsSuggestion
}

}
Expand Down
24 changes: 8 additions & 16 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,6 @@
* [#6750](https://github.com/TouK/nussknacker/pull/6750) Add varargs to `#COLLECTION.concat` and `#COLLECTION.merge`.
* [#6778](https://github.com/TouK/nussknacker/pull/6778) SpeL: check for methods if a property for a given name does not exist.
* [#6769](https://github.com/TouK/nussknacker/pull/6769) Added possibility to choose presets and define lists for Long typed parameter inputs in fragments.
* [#6807](https://github.com/TouK/nussknacker/pull/6807) Add conversion functions to primitives to: `#CONV`:
* toNumberOrNull
* toString
* toBoolean
* toBooleanOrNull
* toInteger
* toIntegerOrNull
* toLong
* toLongOrNull
* toDouble
* toDoubleOrNull
* toBigInteger
* toBigIntegerOrNull
* toBigDecimal
* toBigDecimalOrNull
* [#6995](https://github.com/TouK/nussknacker/pull/6995) Add `toJson` and `toJsonString` conversions (in the `#CONV` helper)
* [#6995](https://github.com/TouK/nussknacker/pull/6995) Add `#BASE64` helper to decode/encode Base64 values
* [#6826](https://github.com/TouK/nussknacker/pull/6826) Security fix: added validation of expression used inside
Expand All @@ -71,7 +56,6 @@
* [#6925](https://github.com/TouK/nussknacker/pull/6925) Fix situation when preset labels were presented as `null` when node didn't pass the validation.
* [#6935](https://github.com/TouK/nussknacker/pull/6935) Spel: Scenario labels added to meta variable - `#meta.scenarioLabels`
* [#6952](https://github.com/TouK/nussknacker/pull/6952) Improvement: TypeInformation support for scala.Option
* [#6840](https://github.com/TouK/nussknacker/pull/6840) Introduce canCastTo, castTo and castToOrNull extension methods in SpeL.
* [#6974](https://github.com/TouK/nussknacker/pull/6974) Add SpeL suggestions for cast methods parameter.
* [#6958](https://github.com/TouK/nussknacker/pull/6958) Add message size limit in the "Kafka" exceptionHandler
* [#6988](https://github.com/TouK/nussknacker/pull/6988) Remove unused API classes: `MultiMap`, `TimestampedEvictableStateFunction`
Expand All @@ -82,6 +66,14 @@
* [#7097](https://github.com/TouK/nussknacker/pull/7097) Flink base types registration mechanism
* [#7021](https://github.com/TouK/nussknacker/pull/7021) Definitions service can return definition without UI config
* [#7010](https://github.com/TouK/nussknacker/pull/7010) Dynamic access allowed via indexer operator (`[]`) on expressions typed as `Unknown`
* [#7063](https://github.com/TouK/nussknacker/pull/7063) Introduce conversion extension methods in SpeL:
* is(className)/to(className)/toOrNull(className)
* isBoolean/toBoolean/toBooleanOrNull
* isLong/toLong/toLongOrNull
* isDouble/toDouble/toDoubleOrNull
* isBigDecimal/toBigDecimal/toBigDecimalOrNull
* isList/toList/toListOrNull
* isMap/toMap/toMapOrNull - the list of key-value pairs or unknown map can be converted to a map.

## 1.17

Expand Down
2 changes: 1 addition & 1 deletion docs/scenarios_authoring/DesignerTipsAndTricks.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ and the expected result list is below.

The expression to use is as follows:

`#myRecord.![{"fieldName": #this.key, "numValue": #CONV.toNumber(#this.value)}]`
`#myRecord.![{"fieldName": #this.key, "numValue": #this.value.toDouble}]`

 
**Not trivial list transformations**
Expand Down
29 changes: 7 additions & 22 deletions docs/scenarios_authoring/Spel.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,13 @@ person2 = name: "John"; age: 24
listOfPersons = {person1, person2}
```

| Expression | Result | Type |
| ------------ | -------- | -------- |
| `{1,2,3,4}.![#this * 2]` | {2, 4, 6, 8} | List[Integer] |
| `#listOfPersons.![#this.name]` | {'Alex', 'John'} | List[String] |
| `#listOfPersons.![#this.age]` | {42, 24} | List[Integer] |
| `#listOfPersons.![7]` | {7, 7} | List[Integer] |

| Expression | Result | Type |
|-----------------------------------------------------------------|----------------------|----------------------|
| `{1,2,3,4}.![#this * 2]` | {2, 4, 6, 8} | List[Integer] |
| `#listOfPersons.![#this.name]` | {'Alex', 'John'} | List[String] |
| `#listOfPersons.![#this.age]` | {42, 24} | List[Integer] |
| `#listOfPersons.![7]` | {7, 7} | List[Integer] |
| `#listOfPersons.![{key: #this.name, value: #this.age}].toMap()` | {Alex: 42, John: 24} | Map[String, Integer] |

For other operations on lists, please see the `#COLLECTION` [helper](#built-in-helpers).

Expand Down Expand Up @@ -308,24 +308,9 @@ Explicit conversions are available in utility classes and build-in java conversi

| Expression | Result | Type |
|-----------------------------------------------------------------|----------------------------|-----------------|
| `#NUMERIC.toNumber('42')` | 42 | Number |
| `#NUMERIC.toNumber('42').toString()` | '42' | String |
| `#DATE_FORMAT.parseOffsetDateTime('2018-10-23T12:12:13+00:00')` | 1540296720000 | OffsetDateTime |
| `#DATE_FORMAT.parseLocalDateTime('2018-10-23T12:12:13')` | 2018-10-23T12:12:13+00:00 | LocalDateTime |

### Casting

When a type cannot be determined by parser, the type is presented as `Unknown`. When we know what the type will be on
runtime, we can cast a given type, and then we can operate on the cast type.

E.g. having a variable `obj` of a type: `List[Unknown]` and we know the elements are strings then we can cast elements
to String: `#obj.![#this.castToOrNull('String')]`.

Available methods:
- `canCastTo` - checks if a type can be cast to a given class.
- `castTo` - casts a type to a given class or throws exception if type cannot be cast.
- `castToOrNull` - casts a type to a given class or return null if type cannot be cast.


## Built-in helpers

Expand Down
Loading

0 comments on commit 333afd6

Please sign in to comment.