diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/generics/MethodTypeInfo.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/generics/MethodTypeInfo.scala index 1d4a20fcead..01d9f9ea303 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/generics/MethodTypeInfo.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/generics/MethodTypeInfo.scala @@ -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) diff --git a/docs/Changelog.md b/docs/Changelog.md index 9510c0939ab..d8834a170de 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -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 diff --git a/docs/scenarios_authoring/Spel.md b/docs/scenarios_authoring/Spel.md index 5cad8fbc1a6..144814c8808 100644 --- a/docs/scenarios_authoring/Spel.md +++ b/docs/scenarios_authoring/Spel.md @@ -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 @@ -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 diff --git a/scenario-compiler/src/main/java/pl/touk/nussknacker/engine/spel/internal/RuntimeConversionHandler.java b/scenario-compiler/src/main/java/pl/touk/nussknacker/engine/spel/internal/ConversionHandler.java similarity index 92% rename from scenario-compiler/src/main/java/pl/touk/nussknacker/engine/spel/internal/RuntimeConversionHandler.java rename to scenario-compiler/src/main/java/pl/touk/nussknacker/engine/spel/internal/ConversionHandler.java index 1d7068da2da..441c06e2f3c 100644 --- a/scenario-compiler/src/main/java/pl/touk/nussknacker/engine/spel/internal/RuntimeConversionHandler.java +++ b/scenario-compiler/src/main/java/pl/touk/nussknacker/engine/spel/internal/ConversionHandler.java @@ -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; @@ -51,12 +51,12 @@ public Object convert( if (source == null) { return null; } - return RuntimeConversionHandler.convert(source); + return ConversionHandler.convertArrayToList(source); } } - public static List convert(Object target) { + public static List convertArrayToList(Object target) { if (target.getClass().getComponentType().isPrimitive()) { int length = Array.getLength(target); List result = new ArrayList<>(length); diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ArrayExt.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ArrayExt.scala index e5dc2f462a4..340be8d7c4c 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ArrayExt.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ArrayExt.scala @@ -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() diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/BooleanConversionExt.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/BooleanConversionExt.scala index a7030dde1bb..568d4387728 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/BooleanConversionExt.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/BooleanConversionExt.scala @@ -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 { diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/CollectionConversionExt.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/CollectionConversionExt.scala index 71f502da128..29c928c7f80 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/CollectionConversionExt.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/CollectionConversionExt.scala @@ -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 diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala index 14f537805de..f23db521f71 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala @@ -3,7 +3,7 @@ package pl.touk.nussknacker.engine.extension import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult, Unknown} import pl.touk.nussknacker.engine.api.util.ReflectUtils import pl.touk.nussknacker.engine.extension.Conversion.toNumber -import pl.touk.nussknacker.engine.spel.internal.RuntimeConversionHandler +import pl.touk.nussknacker.engine.spel.internal.ConversionHandler import pl.touk.nussknacker.engine.util.classes.Extensions.ClassExtensions import scala.util.{Success, Try} @@ -56,7 +56,7 @@ object Conversion { } -object LongConversion extends Conversion { +object ToLongConversion extends Conversion { override type ResultType = JLong override val resultTypeClass: Class[JLong] = classOf[JLong] @@ -74,15 +74,15 @@ object LongConversion extends Conversion { private def toLongEither(value: Any): Either[Throwable, JLong] = { value match { - case v: String => Try(JLong.valueOf(toNumber(v).longValue())).toEither case v: Number => Right(v.longValue()) + case v: String => Try(JLong.valueOf(toNumber(v).longValue())).toEither case _ => Left(new IllegalArgumentException(s"Cannot convert: $value to Long")) } } } -object DoubleConversion extends Conversion { +object ToDoubleConversion extends Conversion { override type ResultType = JDouble override val resultTypeClass: Class[JDouble] = classOf[JDouble] @@ -100,15 +100,15 @@ object DoubleConversion extends Conversion { private def toDoubleEither(value: Any): Either[Throwable, JDouble] = { value match { - case v: String => Try(JDouble.valueOf(toNumber(v).doubleValue())).toEither case v: Number => Right(v.doubleValue()) + case v: String => Try(JDouble.valueOf(toNumber(v).doubleValue())).toEither case _ => Left(new IllegalArgumentException(s"Cannot convert: $value to Double")) } } } -object BigDecimalConversion extends Conversion { +object ToBigDecimalConversion extends Conversion { override type ResultType = JBigDecimal override val resultTypeClass: Class[JBigDecimal] = classOf[JBigDecimal] @@ -126,16 +126,16 @@ object BigDecimalConversion extends Conversion { private def toBigDecimalEither(value: Any): Either[Throwable, JBigDecimal] = value match { - case v: String => Try(new JBigDecimal(v)).toEither - case v: JBigInteger => Right(new JBigDecimal(v)) case v: JBigDecimal => Right(v) + case v: JBigInteger => Right(new JBigDecimal(v)) + case v: String => Try(new JBigDecimal(v)).toEither case v: Number => Try(new JBigDecimal(v.toString)).toEither case _ => Left(new IllegalArgumentException(s"Cannot convert: $value to BigDecimal")) } } -object BooleanConversion extends Conversion { +object ToBooleanConversion extends Conversion { private val cannotConvertException = (value: Any) => new IllegalArgumentException(s"Cannot convert: $value to Boolean") @@ -155,8 +155,8 @@ object BooleanConversion extends Conversion { } private def convertToBoolean(value: Any): Either[Throwable, JBoolean] = value match { - case s: String => stringToBoolean(s).toRight(cannotConvertException(value)) case b: JBoolean => Right(b) + case s: String => stringToBoolean(s).toRight(cannotConvertException(value)) case _ => Left(cannotConvertException(value)) } @@ -171,7 +171,7 @@ object BooleanConversion extends Conversion { } -object StringConversion extends Conversion { +object ToStringConversion extends Conversion { override type ResultType = String override val resultTypeClass: Class[ResultType] = classOf[String] @@ -180,7 +180,7 @@ object StringConversion extends Conversion { override def convertOrNull(value: Any): ResultType = value.toString } -object MapConversion extends Conversion { +object ToMapConversion extends Conversion { private[extension] val keyName = "key" private[extension] val valueName = "value" private[extension] val keyAndValueNames = JSet.of(keyName, valueName) @@ -190,9 +190,9 @@ object MapConversion extends Conversion { override def typingResult: TypingResult = Typed.genericTypeClass(resultTypeClass, List(Unknown, Unknown)) override def canConvert(value: Any): JBoolean = value match { - case c: JCollection[_] => canConvertToMap(c) case _: JMap[_, _] => true - case a: Array[_] => canConvertToMap(RuntimeConversionHandler.convert(a)) + case c: JCollection[_] => canConvertToMap(c) + case a: Array[_] => canConvertToMap(ConversionHandler.convertArrayToList(a)) case _ => false } @@ -208,13 +208,13 @@ object MapConversion extends Conversion { private def convertToMap[K, V](value: Any): Either[Throwable, JMap[K, V]] = value match { + case m: JMap[K, V] @unchecked => Right(m) + case a: Array[_] => convertToMap[K, V](ConversionHandler.convertArrayToList(a)) case c: JCollection[JMap[_, _] @unchecked] if canConvertToMap(c) => val map = new JHashMap[K, V]() c.forEach(e => map.put(e.get(keyName).asInstanceOf[K], e.get(valueName).asInstanceOf[V])) Right(map) - case m: JMap[K, V] @unchecked => Right(m) - case a: Array[_] => convertToMap[K, V](RuntimeConversionHandler.convert(a)) - case x => Left(new IllegalArgumentException(s"Cannot convert: $x to a Map")) + case x => Left(new IllegalArgumentException(s"Cannot convert: $x to a Map")) } private def canConvertToMap(c: JCollection[_]): Boolean = c.isEmpty || c @@ -226,8 +226,8 @@ object MapConversion extends Conversion { } -object ListConversion extends Conversion { - private[extension] val collectionClass = classOf[JCollection[_]] +object ToListConversion extends Conversion { + private val collectionClass = classOf[JCollection[_]] override type ResultType = JList[_] override val resultTypeClass: Class[JList[_]] = classOf[JList[_]] @@ -249,7 +249,7 @@ object ListConversion extends Conversion { value match { case l: JList[T @unchecked] => Right(l) case c: JCollection[T @unchecked] => Right(new JArrayList[T](c)) - case a: Array[T @unchecked] => Right(RuntimeConversionHandler.convert(a).asInstanceOf[JList[T]]) + case a: Array[T @unchecked] => Right(ConversionHandler.convertArrayToList(a).asInstanceOf[JList[T]]) case x => Left(new IllegalArgumentException(s"Cannot convert: $x to a List")) } } diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ConversionExt.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ConversionExt.scala index 7282a621999..2277a923157 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ConversionExt.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ConversionExt.scala @@ -67,13 +67,13 @@ object ConversionExt extends ExtensionMethodsHandler { private val convertMethodsNames = definitions.keySet private val conversionsRegistry: List[Conversion] = List( - LongConversion, - DoubleConversion, - BigDecimalConversion, - BooleanConversion, - StringConversion, - MapConversion, - ListConversion, + ToLongConversion, + ToDoubleConversion, + ToBigDecimalConversion, + ToBooleanConversion, + ToStringConversion, + ToMapConversion, + ToListConversion, ) private val conversionsByType: Map[String, Conversion] = conversionsRegistry diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/NumericConversionExt.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/NumericConversionExt.scala index 6abc36ba425..47f7caa1182 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/NumericConversionExt.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/NumericConversionExt.scala @@ -10,15 +10,15 @@ import java.math.{BigDecimal => JBigDecimal} class NumericConversionExt(target: Any) { - def isLong(): JBoolean = LongConversion.canConvert(target) - def toLong(): JLong = LongConversion.convert(target) - def toLongOrNull(): JLong = LongConversion.convertOrNull(target) - def isDouble(): JBoolean = DoubleConversion.canConvert(target) - def toDouble(): JDouble = DoubleConversion.convert(target) - def toDoubleOrNull(): JDouble = DoubleConversion.convertOrNull(target) - def isBigDecimal(): JBoolean = BigDecimalConversion.canConvert(target) - def toBigDecimal(): JBigDecimal = BigDecimalConversion.convert(target) - def toBigDecimalOrNull(): JBigDecimal = BigDecimalConversion.convertOrNull(target) + def isLong(): JBoolean = ToLongConversion.canConvert(target) + def toLong(): JLong = ToLongConversion.convert(target) + def toLongOrNull(): JLong = ToLongConversion.convertOrNull(target) + def isDouble(): JBoolean = ToDoubleConversion.canConvert(target) + def toDouble(): JDouble = ToDoubleConversion.convert(target) + def toDoubleOrNull(): JDouble = ToDoubleConversion.convertOrNull(target) + def isBigDecimal(): JBoolean = ToBigDecimalConversion.canConvert(target) + def toBigDecimal(): JBigDecimal = ToBigDecimalConversion.convert(target) + def toBigDecimalOrNull(): JBigDecimal = ToBigDecimalConversion.convertOrNull(target) } object NumericConversionExt extends ExtensionMethodsHandler { diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/internal/DefaultSpelConversionsProvider.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/internal/DefaultSpelConversionsProvider.scala index 7f7d43206b4..119d1acb935 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/internal/DefaultSpelConversionsProvider.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/internal/DefaultSpelConversionsProvider.scala @@ -31,7 +31,7 @@ class DefaultSpelConversionsProvider extends SpelConversionsProvider { service.addConverter(new ObjectToArrayConverter(service)) // For purpose of concise usage of numbers in spel templates service.addConverter(classOf[Number], classOf[String], (source: Number) => source.toString) - service.addConverter(new RuntimeConversionHandler.ArrayToListConverter(service)) + service.addConverter(new ConversionHandler.ArrayToListConverter(service)) service } diff --git a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala index c0c6a27dd9f..207881cdf31 100644 --- a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala +++ b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala @@ -1718,6 +1718,12 @@ class SpelExpressionSpec extends AnyFunSuite with Matchers with ValidatedValuesD } } + test("should convert a set to a list") { + val parsed = parse[Any]("#setVal.toList()", ctx).validValue + parsed.returnType shouldBe Typed.genericTypeClass[JList[_]](List(Typed.typedClass[String])) + parsed.evaluateSync[Any](ctx) shouldBe JList.of("a") + } + } case class SampleObject(list: java.util.List[SampleValue])