Skip to content

Commit

Permalink
review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Łukasz Bigorajski committed Oct 28, 2024
1 parent 4ea7c6e commit 2b5f1d9
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 119 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
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]]

val noArgTypeInfo = MethodTypeInfo(
noVarArgs = Nil,
varArg = None,
result = Unknown
)

def fromList(lst: List[Parameter], varArgs: Boolean, result: TypingResult): MethodTypeInfo = (varArgs, lst) match {
case (true, noVarArgParameters :+ Parameter(paramName, TypedClass(`arrayClass`, varArgType :: Nil))) =>
MethodTypeInfo(noVarArgParameters, Some(Parameter(paramName, varArgType)), result)
Expand Down
8 changes: 8 additions & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@
* [#7000](https://github.com/TouK/nussknacker/pull/7000) Show all possible options for dictionary editor on open.
* [#6979](https://github.com/TouK/nussknacker/pull/6979) Introduces an activities panel that provides information about all system activities.
* [#7058](https://github.com/TouK/nussknacker/pull/7058) Performance optimization: Add missing Flink TypeInformation for better serialization
* [#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 - a collection or unknown collection can be converted to a list.
* isMap/toMap/toMapOrNull - the list of key-value pairs or unknown map can be converted to a map.

## 1.17

Expand Down
52 changes: 22 additions & 30 deletions docs/scenarios_authoring/Spel.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ listOfPersons = {person1, person2}
| `#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).

### Safe navigation
Expand Down Expand Up @@ -277,35 +276,28 @@ More usage examples can be found in [this section](#conversions-between-datetime

#### Explicit conversions

Explicit conversions are available in utility classes and build-in java conversion mechanisms:

| 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 |
| `'true'.toBoolean` | true | Boolean |
| `'11'.toLong` | 11 | Long |
| `'1.1'.toDouble` | 1.1 | Double |
| `2.1.toBigDecimal` | 2.1 | BigDecimal |

Functions which start with `to`, e.g. `toBoolean`, have their equivalents:
- to check if a given value can be converted to appropriate type, e.g. `'true'.isBoolean`.
- and to return null if conversion fails, e.g. `'false'.toBooleanOrNull'`.

### 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.
Explicit conversions are available in utility classes, build-in java conversion mechanisms and via extension methods:

| 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 |
| `'true'.toBoolean` | true | Boolean |
| `'11'.toLong` | 11 | Long |
| `'1.1'.toDouble` | 1.1 | Double |
| `2.1.toBigDecimal` | 2.1 | BigDecimal |
| `{a: 1, b: 2}.keySet.toList` | {'a', 'b'} | List[String] |

Functions which start with `to` are extension methods which allows convert value to appropriate type.
Except function e.g. `toLong` which is shorthand of: `to('Long')` we have also:
- `isLong` which checks if a given value can be converted to Long.
- `toLongOrNull` which returns null on failed conversion instead of throwing exception.

Additionally, we can use generic conversion functions like: `is(className)`, `to(className)` or `toOrNull(className)`,
e.g. `is('Long'), to('Long), toOrNull('Long')`. Thanks to them, we have a single function to make all available
conversions.

## Built-in helpers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.List;
import java.util.Set;

public class RuntimeConversionHandler {
public class ConversionHandler {
public static final class ArrayToListConverter implements ConditionalGenericConverter {

private final ConversionService conversionService;
Expand Down Expand Up @@ -51,12 +51,12 @@ public Object convert(
if (source == null) {
return null;
}
return RuntimeConversionHandler.convert(source);
return ConversionHandler.convertArrayToList(source);
}

}

public static List<Object> convert(Object target) {
public static List<Object> convertArrayToList(Object target) {
if (target.getClass().getComponentType().isPrimitive()) {
int length = Array.getLength(target);
List<Object> result = new ArrayList<>(length);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package pl.touk.nussknacker.engine.extension

import pl.touk.nussknacker.engine.definition.clazz.{ClassDefinitionSet, MethodDefinition}
import pl.touk.nussknacker.engine.spel.internal.RuntimeConversionHandler
import pl.touk.nussknacker.engine.spel.internal.ConversionHandler

import java.util
import java.util.{List => JList}

class ArrayExt(target: Any) extends util.AbstractList[Object] {
private val asList = RuntimeConversionHandler.convert(target)
private val asList = ConversionHandler.convertArrayToList(target)

override def get(index: Int): AnyRef = asList.get(index)
override def size(): Int = asList.size()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import pl.touk.nussknacker.engine.definition.clazz.{ClassDefinitionSet, MethodDe
import java.lang.{Boolean => JBoolean}

class BooleanConversionExt(target: Any) {
def isBoolean(): JBoolean = BooleanConversion.canConvert(target)
def toBoolean(): JBoolean = BooleanConversion.convert(target)
def toBooleanOrNull(): JBoolean = BooleanConversion.convertOrNull(target)
def isBoolean(): JBoolean = ToBooleanConversion.canConvert(target)
def toBoolean(): JBoolean = ToBooleanConversion.convert(target)
def toBooleanOrNull(): JBoolean = ToBooleanConversion.convertOrNull(target)
}

object BooleanConversionExt extends ExtensionMethodsHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,78 +5,76 @@ import cats.implicits.catsSyntaxValidatedId
import pl.touk.nussknacker.engine.api.generics.{GenericFunctionTypingError, MethodTypeInfo}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypedClass, TypedObjectTypingResult, TypingResult, Unknown}
import pl.touk.nussknacker.engine.definition.clazz.{ClassDefinitionSet, FunctionalMethodDefinition, MethodDefinition}
import pl.touk.nussknacker.engine.extension.ListConversion.collectionClass
import pl.touk.nussknacker.engine.extension.MapConversion.{keyName, valueName}
import pl.touk.nussknacker.engine.extension.ToMapConversion.{keyName, valueName}
import pl.touk.nussknacker.engine.util.classes.Extensions.ClassExtensions

import java.util.{List => JList, Map => JMap}
import java.util.{Collection => JCollection, List => JList, Map => JMap}

class CollectionConversionExt(target: Any) {

def isList(): Boolean = ListConversion.canConvert(target)
def toList(): JList[_] = ListConversion.convert(target)
def toListOrNull(): JList[_] = ListConversion.convertOrNull(target)
def isMap(): Boolean = MapConversion.canConvert(target)
def toMap(): JMap[_, _] = MapConversion.convert(target)
def toMapOrNull(): JMap[_, _] = MapConversion.convertOrNull(target)
def isList(): Boolean = ToListConversion.canConvert(target)
def toList(): JList[_] = ToListConversion.convert(target)
def toListOrNull(): JList[_] = ToListConversion.convertOrNull(target)
def isMap(): Boolean = ToMapConversion.canConvert(target)
def toMap(): JMap[_, _] = ToMapConversion.convert(target)
def toMapOrNull(): JMap[_, _] = ToMapConversion.convertOrNull(target)

}

object CollectionConversionExt extends ExtensionMethodsHandler {
private val unknownClass = classOf[Object]
private val booleanTyping = Typed.typedClass[Boolean]
private val unknownClass = classOf[Object]
private val booleanTyping = Typed.typedClass[Boolean]
private val collectionClass = classOf[JCollection[_]]

private val isMapMethodDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toMapTypeFunction(invocationTarget).map(_ => booleanTyping),
signature = MethodTypeInfo.noArgTypeInfo,
name = "isMap",
description = Some("Check whether can be convert to a map")
)

private val toMapDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toMapTypeFunction(invocationTarget),
signature = MethodTypeInfo(
noVarArgs = Nil,
varArg = None,
result = Unknown
),
signature = MethodTypeInfo.noArgTypeInfo,
name = "toMap",
description = Option("Convert to a map or throw exception in case of failure")
)

private val toListDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toListTypeFunction(invocationTarget),
signature = MethodTypeInfo(
noVarArgs = Nil,
varArg = None,
result = Unknown
),
name = "toList",
description = Option("Convert to a list or throw exception in case of failure")
)

private val isMapMethodDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toMapTypeFunction(invocationTarget).map(_ => booleanTyping),
signature = MethodTypeInfo(
noVarArgs = Nil,
varArg = None,
result = Unknown
),
name = "isMap",
description = Some("Check whether can be convert to a map")
private val toMapOrNullDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toMapTypeFunction(invocationTarget),
signature = MethodTypeInfo.noArgTypeInfo,
name = "toMapOrNull",
description = Option("Convert to a map or null in case of failure")
)

private val isListMethodDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toListTypeFunction(invocationTarget).map(_ => booleanTyping),
signature = MethodTypeInfo(
noVarArgs = Nil,
varArg = None,
result = Unknown
),
signature = MethodTypeInfo.noArgTypeInfo,
name = "isList",
description = Some("Check whether can be convert to a list")
)

private val toListDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toListTypeFunction(invocationTarget),
signature = MethodTypeInfo.noArgTypeInfo,
name = "toList",
description = Option("Convert to a list or throw exception in case of failure")
)

private val toListOrNullDefinition = FunctionalMethodDefinition(
typeFunction = (invocationTarget, _) => toListTypeFunction(invocationTarget),
signature = MethodTypeInfo.noArgTypeInfo,
name = "toListOrNull",
description = Option("Convert to a list or null in case of failure")
)

private val definitions = List(
isMapMethodDefinition,
toMapDefinition,
toMapDefinition.copy(name = "toMapOrNull", description = Some("Convert to a map or null in case of failure")),
toMapOrNullDefinition,
isListMethodDefinition,
toListDefinition,
toListDefinition.copy(name = "toListOrNull", description = Some("Convert to a list or null in case of failure")),
toListOrNullDefinition,
).groupBy(_.name)

override type ExtensionMethodInvocationTarget = CollectionConversionExt
Expand Down
Loading

0 comments on commit 2b5f1d9

Please sign in to comment.