From 8d59bd238ea1dde10a536da2f7385284243b69ac Mon Sep 17 00:00:00 2001 From: Udo Klimaschewski Date: Wed, 20 Sep 2023 15:41:15 +0200 Subject: [PATCH] 3.1.x (#396) - introduces a configuration for the date-time zone id - refactors EvaluationValue conversion to use a configurable converter - deprecates evaluation value constructor not using a configuration - deprecates EvaluationValue constructor using double value and MathContext --- docs/concepts/changes.md | 2 +- docs/concepts/date_time_duration.md | 97 +++++++ docs/references/functions.md | 4 +- .../java/com/ezylang/evalex/Expression.java | 34 +-- .../config/ExpressionConfiguration.java | 55 ++-- .../data/DefaultEvaluationValueConverter.java | 88 +++++++ .../ezylang/evalex/data/EvaluationValue.java | 249 +++++++++--------- .../data/EvaluationValueConverterIfc.java | 35 +++ .../data/conversion/ArrayConverter.java | 37 +++ .../data/conversion/BooleanConverter.java | 32 +++ .../evalex/data/conversion/ConverterIfc.java | 47 ++++ .../data/conversion/DateTimeConverter.java | 62 +++++ .../data/conversion/DurationConverter.java | 33 +++ .../conversion/ExpressionNodeConverter.java | 33 +++ .../data/conversion/NumberConverter.java | 60 +++++ .../data/conversion/StringConverter.java | 43 +++ .../data/conversion/StructureConverter.java | 39 +++ .../evalex/functions/basic/AbsFunction.java | 2 +- .../functions/basic/CeilingFunction.java | 2 +- .../evalex/functions/basic/FactFunction.java | 2 +- .../evalex/functions/basic/FloorFunction.java | 2 +- .../evalex/functions/basic/MaxFunction.java | 2 +- .../evalex/functions/basic/MinFunction.java | 2 +- .../evalex/functions/basic/NotFunction.java | 2 +- .../evalex/functions/basic/RoundFunction.java | 2 +- .../evalex/functions/basic/SqrtFunction.java | 4 +- .../evalex/functions/basic/SumFunction.java | 2 +- .../AbstractDateTimeParseFunction.java | 4 +- .../datetime/DateTimeFormatFunction.java | 4 +- .../datetime/DateTimeFromEpochFunction.java | 2 +- .../functions/datetime/DateTimeFunction.java | 4 +- .../datetime/DateTimeToEpochFunction.java | 2 +- .../datetime/DurationFromDaysFunction.java | 2 +- .../datetime/DurationFromMillisFunction.java | 2 +- .../datetime/DurationParseFunction.java | 2 +- .../functions/string/StringContains.java | 2 +- .../functions/string/StringLowerFunction.java | 2 +- .../functions/string/StringUpperFunction.java | 2 +- .../arithmetic/InfixDivisionOperator.java | 2 +- .../arithmetic/InfixMinusOperator.java | 10 +- .../arithmetic/InfixModuloOperator.java | 2 +- .../InfixMultiplicationOperator.java | 2 +- .../arithmetic/InfixPlusOperator.java | 10 +- .../arithmetic/InfixPowerOfOperator.java | 2 +- .../arithmetic/PrefixMinusOperator.java | 2 +- .../arithmetic/PrefixPlusOperator.java | 2 +- .../operators/booleans/InfixAndOperator.java | 2 +- .../booleans/InfixEqualsOperator.java | 2 +- .../booleans/InfixGreaterEqualsOperator.java | 2 +- .../booleans/InfixGreaterOperator.java | 2 +- .../booleans/InfixLessEqualsOperator.java | 2 +- .../operators/booleans/InfixLessOperator.java | 2 +- .../booleans/InfixNotEqualsOperator.java | 2 +- .../operators/booleans/InfixOrOperator.java | 2 +- .../operators/booleans/PrefixNotOperator.java | 2 +- .../config/TestConfigurationProvider.java | 2 +- .../DefaultEvaluationValueConverterTest.java | 52 ++++ .../evalex/data/EvaluationValueTest.java | 62 ++++- .../data/conversion/ArrayConverterTest.java | 75 ++++++ .../data/conversion/BooleanConverterTest.java | 59 +++++ .../conversion/DateTimeConverterTest.java | 152 +++++++++++ .../data/conversion/DuratinCoverterTest.java | 53 ++++ .../ExpressionNodeConverterTest.java | 56 ++++ .../data/conversion/NumberConverterTest.java | 122 +++++++++ .../data/conversion/StringConverterTest.java | 67 +++++ .../conversion/StructureConverterTest.java | 77 ++++++ .../functions/basic/BasicFunctionsTest.java | 7 +- .../datetime/DateTimeFunctionsTest.java | 2 +- .../arithmetic/ArithmeticOperatorsTest.java | 4 +- 69 files changed, 1605 insertions(+), 235 deletions(-) create mode 100644 docs/concepts/date_time_duration.md create mode 100644 src/main/java/com/ezylang/evalex/data/DefaultEvaluationValueConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/EvaluationValueConverterIfc.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/ArrayConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/BooleanConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/ConverterIfc.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/DateTimeConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/DurationConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/NumberConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/StringConverter.java create mode 100644 src/main/java/com/ezylang/evalex/data/conversion/StructureConverter.java create mode 100644 src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/ArrayConverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/BooleanConverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/DateTimeConverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/DuratinCoverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/NumberConverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/StringConverterTest.java create mode 100644 src/test/java/com/ezylang/evalex/data/conversion/StructureConverterTest.java diff --git a/docs/concepts/changes.md b/docs/concepts/changes.md index 6b4b2a7..93325ad 100644 --- a/docs/concepts/changes.md +++ b/docs/concepts/changes.md @@ -2,7 +2,7 @@ layout: default title: Major Changes parent: Concepts -nav_order: 4 +nav_order: 5 --- ## Major Changes From Version 2 to 3 diff --git a/docs/concepts/date_time_duration.md b/docs/concepts/date_time_duration.md new file mode 100644 index 0000000..480aff6 --- /dev/null +++ b/docs/concepts/date_time_duration.md @@ -0,0 +1,97 @@ +--- +layout: default +title: Working with Date-Times and Duration +parent: Concepts +nav_order: 4 +--- + +## Working with Date-Times and Duration + +Since version 3.1.0 of EvalEx, there are two additional data types _DATE_TIME_ and _DURATION_. + +_DATE_TIME_ values are stored internally as _java.time.Instant_ values, _DURATION_ values are stored as +_java.time.Duration_ values. + +A _DATE_TIME_ instant is instantaneous point on the time-line, it holds no information about the time zone. +Time zones come into play, when converting local dates-times to instants and vice versa. +The same instant can have different local date-time values, depending on the destination time zone. +The precision of a _DATE_TIME_ is up to nanoseconds. + +A _DURATION_ is a certain amount of time, like e.g. "3 hours, 15 minutes and 6 seconds". +The smallest amount of a duration is 1 nanosecond. + +### Arithmetic operations with _DATE_TIME_ and _DURATION_. + +The infix plus and minus operators can be used to do calculations with _DATE_TIME_ and _DURATION_ values. +The outcome of the operation depends on the operator types: + +#### Addition +| Left Operand | Right Operand | Result | +|--------------|---------------|------------------------------------------------------------------------| +| _DATE_TIME_ | _DURATION_ | A new _DATE_TIME_ where the duration is added to the date. | +| _DURATION_ | _DURATION_ | A new duration, which is the sum of both durations. | +| _DATE_TIME_ | _NUMBER_ | A new _DATE_TIME_ with the amount of a duration in milliseconds added. | + +All other combinations of _DATE_TIME_ and _DURATION_ with other types will do a string concatenation. + +Example. Adding a duration to a date-time: +```java +Instant start = Instant.parse("2023-12-03T23:15:30.00Z"); +Duration duration = Duration.ofHours(3); + +Expression expression = new Expression("start + duration"); +EvaluationValue result = + expression + .with("start", start) + .and("duration", duration) + .evaluate(); +System.out.println(result); // will print "EvaluationValue(value=2023-12-04T02:15:30Z, dataType=DATE_TIME)" +``` + +#### Subtraction +| Left Operand | Right Operand | Result | +|--------------|---------------|-----------------------------------------------------------------------------| +| _DATE_TIME_ | _DATE_TIME_ | A duration which reflects the difference between the two date-times. | +| _DATE_TIME_ | _DURATION_ | A new _DATE_TIME_ where the duration is subtracted from the date. | +| _DURATION_ | _DURATION_ | A new duration, which is the difference of both durations. | +| _DATE_TIME_ | _NUMBER_ | A new _DATE_TIME_ with the amount of a duration in milliseconds subtracted. | + +All other combinations of _DATE_TIME_ and _DURATION_ with other types will throw an _EvaluationException_. + +Example. Find out the duration between two date-times: +```java +Instant start = Instant.parse("2023-12-05T11:20:00.00Z"); +Instant end = Instant.parse("2023-12-04T23:15:30.00Z"); + +Expression expression = new Expression("start - end"); +EvaluationValue result = expression + .with("start", start) + .and("end", end) + .evaluate(); +System.out.println(result); // will print "EvaluationValue(value=PT12H4M30S, dataType=DURATION)" +``` + +The string representation of a duration is here in SO format, meaning 12 hours, 4 minutes and 30 seconds. + +### Passing other Date-Time Types as variables + +Instead of passing _java.time.Instant_ values for _DATE_TIME_ values, you can pass also the following Java data types. +They will be converted automatically to _java.time.Instant_ values. + +| Input type | Conversion note | +|----------------|---------------------------------------------------------------------------------------------------------------------------| +| ZonedDateTime | Directly converted. | +| OffsetDateTime | Directly converted. | +| LocalDate | Converted using the configured time zone. Defaults to the systems time zone.
The time is set to beginning of the day. | +| LocalDateTime | Converted using the configured time zone. Defaults to the systems time zone. | +| Date | Directly converted. | +| Calendar | Directly converted. | + +### New Date-Time Functions + +In addition to the possibility to add and subtract with _DATE_TIME_ and _DURATION_ values, there are also several new +functions to work with date-times. Most of them allow to create, parse and format date-time and duration values. + +See Chapter [Date Time Functions](../references/functions.html#date-time-Functions) + +### Configuration Changes \ No newline at end of file diff --git a/docs/references/functions.md b/docs/references/functions.md index 7db0e91..8289e7d 100644 --- a/docs/references/functions.md +++ b/docs/references/functions.md @@ -36,7 +36,7 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons | STR_LOWER(value) | Converts the given value to lower case | | STR_UPPER(value) | Converts the given value to upper case | -### trigonometric Functions +### Trigonometric Functions | Name | Description | |--------------|------------------------------------------------------------------------------------------------| @@ -75,7 +75,7 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons | TANH(value) | Returns the hyperbolic tangent of a value | | TANR(value) | Returns the tangent of an angle (in radians) | -### DateTime Functions +### Date Time Functions | Name | Description | |---------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/src/main/java/com/ezylang/evalex/Expression.java b/src/main/java/com/ezylang/evalex/Expression.java index aa9143f..22ee219 100644 --- a/src/main/java/com/ezylang/evalex/Expression.java +++ b/src/main/java/com/ezylang/evalex/Expression.java @@ -19,18 +19,9 @@ import com.ezylang.evalex.config.ExpressionConfiguration; import com.ezylang.evalex.data.DataAccessorIfc; import com.ezylang.evalex.data.EvaluationValue; import com.ezylang.evalex.functions.FunctionIfc; -import com.ezylang.evalex.parser.ASTNode; -import com.ezylang.evalex.parser.ParseException; -import com.ezylang.evalex.parser.ShuntingYardConverter; -import com.ezylang.evalex.parser.Token; -import com.ezylang.evalex.parser.Tokenizer; +import com.ezylang.evalex.parser.*; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; import lombok.Getter; /** @@ -100,7 +91,7 @@ public class Expression { result = EvaluationValue.numberOfString(token.getValue(), configuration.getMathContext()); break; case STRING_LITERAL: - result = new EvaluationValue(token.getValue()); + result = EvaluationValue.stringValue(token.getValue()); break; case VARIABLE_OR_CONSTANT: result = getVariableOrConstant(token); @@ -158,7 +149,7 @@ public class Expression { List parameterResults = new ArrayList<>(); for (int i = 0; i < startNode.getParameters().size(); i++) { if (token.getFunctionDefinition().isParameterLazy(i)) { - parameterResults.add(new EvaluationValue(startNode.getParameters().get(i))); + parameterResults.add(convertValue(startNode.getParameters().get(i))); } else { parameterResults.add(evaluateSubtree(startNode.getParameters().get(i))); } @@ -219,7 +210,7 @@ public class Expression { if (configuration.isStripTrailingZeros()) { bigDecimal = bigDecimal.stripTrailingZeros(); } - return new EvaluationValue(bigDecimal); + return EvaluationValue.numberValue(bigDecimal); } /** @@ -266,7 +257,7 @@ public class Expression { String.format("Can't set value for constant '%s'", variable)); } } - getDataAccessor().setData(variable, new EvaluationValue(value)); + getDataAccessor().setData(variable, convertValue(value)); return this; } @@ -322,7 +313,18 @@ public class Expression { * @return An {@link EvaluationValue} of type {@link EvaluationValue.DataType#NUMBER}. */ public EvaluationValue convertDoubleValue(double value) { - return new EvaluationValue(value, configuration.getMathContext()); + return convertValue(value); + } + + /** + * Converts an object value to an {@link EvaluationValue} by considering the configuration {@link + * EvaluationValue(Object, ExpressionConfiguration)}. + * + * @param value The object value to covert. + * @return An {@link EvaluationValue} of the detected type and value. + */ + public EvaluationValue convertValue(Object value) { + return new EvaluationValue(value, configuration); } /** diff --git a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java index 86fd4a6..53b5549 100644 --- a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java +++ b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java @@ -15,9 +15,7 @@ */ package com.ezylang.evalex.config; -import com.ezylang.evalex.data.DataAccessorIfc; -import com.ezylang.evalex.data.EvaluationValue; -import com.ezylang.evalex.data.MapBasedDataAccessor; +import com.ezylang.evalex.data.*; import com.ezylang.evalex.functions.FunctionIfc; import com.ezylang.evalex.functions.basic.*; import com.ezylang.evalex.functions.datetime.*; @@ -75,6 +73,9 @@ public class ExpressionConfiguration { public static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(68, RoundingMode.HALF_EVEN); + /** The default zone id is the systemd default zone ID. */ + public static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault(); + /** The operator dictionary holds all operators that will be allowed in an expression. */ @Builder.Default @Getter @@ -227,8 +228,14 @@ public class ExpressionConfiguration { */ @Builder.Default @Getter private final boolean allowOverwriteConstants = true; - /** Set the default zone id. By default, the system default zone id is used. */ - @Builder.Default @Getter private final ZoneId defaultZoneId = ZoneId.systemDefault(); + /** The time zone id. By default, the system default zone id is used. */ + @Builder.Default @Getter private final ZoneId zoneId = DEFAULT_ZONE_ID; + + /** The converter to use when converting different data types to an {@link EvaluationValue}. */ + @Builder.Default @Getter + private final EvaluationValueConverterIfc evaluationValueConverter = + new DefaultEvaluationValueConverter(); + /** * Convenience method to create a default configuration. * @@ -243,14 +250,12 @@ public class ExpressionConfiguration { * * @param operators variable number of arguments with a map entry holding the operator name and * implementation.
- * Example: - *
-   *                                                        ExpressionConfiguration.defaultConfiguration()
-   *                                                           .withAdditionalOperators(
-   *                                                               Map.entry("++", new PrefixPlusPlusOperator()),
-   *                                                               Map.entry("++", new PostfixPlusPlusOperator()));
-   *                                                        
- * + * Example: + * ExpressionConfiguration.defaultConfiguration() + * .withAdditionalOperators( + * Map.entry("++", new PrefixPlusPlusOperator()), + * Map.entry("++", new PostfixPlusPlusOperator())); + * * @return The modified configuration, to allow chaining of methods. */ @SafeVarargs @@ -266,14 +271,12 @@ public class ExpressionConfiguration { * * @param functions variable number of arguments with a map entry holding the functions name and * implementation.
- * Example: - *
-   *                                                        ExpressionConfiguration.defaultConfiguration()
-   *                                                           .withAdditionalFunctions(
-   *                                                               Map.entry("save", new SaveFunction()),
-   *                                                               Map.entry("update", new UpdateFunction()));
-   *                                                        
- * + * Example: + * ExpressionConfiguration.defaultConfiguration() + * .withAdditionalFunctions( + * Map.entry("save", new SaveFunction()), + * Map.entry("update", new UpdateFunction())); + * * @return The modified configuration, to allow chaining of methods. */ @SafeVarargs @@ -288,19 +291,19 @@ public class ExpressionConfiguration { Map constants = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - constants.put("TRUE", new EvaluationValue(true)); - constants.put("FALSE", new EvaluationValue(false)); + constants.put("TRUE", EvaluationValue.booleanValue(true)); + constants.put("FALSE", EvaluationValue.booleanValue(false)); constants.put( "PI", - new EvaluationValue( + EvaluationValue.numberValue( new BigDecimal( "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"))); constants.put( "E", - new EvaluationValue( + EvaluationValue.numberValue( new BigDecimal( "2.71828182845904523536028747135266249775724709369995957496696762772407663"))); - constants.put("NULL", new EvaluationValue(null)); + constants.put("NULL", EvaluationValue.nullValue()); return constants; } diff --git a/src/main/java/com/ezylang/evalex/data/DefaultEvaluationValueConverter.java b/src/main/java/com/ezylang/evalex/data/DefaultEvaluationValueConverter.java new file mode 100644 index 0000000..b05fcd3 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/DefaultEvaluationValueConverter.java @@ -0,0 +1,88 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.conversion.*; +import java.util.Arrays; +import java.util.List; + +/** + * The default implementation of the {@link EvaluationValueConverterIfc}, used in the standard + * configuration. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Input typeConverter used
BigDecimalNumberConverter
Long, longNumberConverter
Integer, intNumberConverter
Short, shortNumberConverter
Byte, byteNumberConverter
Double, doubleNumberConverter *
Float, floatNumberConverter *
CharSequence , StringStringConverter
Boolean, booleanBooleanConverter
InstantDateTimeConverter
DateDateTimeConverter
CalendarDateTimeConverter
ZonedDateTimeDateTimeConverter
LocalDateDateTimeConverter - the configured zone id will be used for conversion
LocalDateTimeDateTimeConverter - the configured zone id will be used for conversion
OffsetDateTimeDateTimeConverter
DurationDurationConverter
ASTNodeASTNode
List<?>ArrayConverter - each entry will be converted
Map<?,?>StructureConverter - each entry will be converted.
+ * + * * Be careful with conversion problems when using float or double, which are fractional + * numbers. A (float)0.1 is e.g. converted to 0.10000000149011612 + */ +public class DefaultEvaluationValueConverter implements EvaluationValueConverterIfc { + + static List converters = + Arrays.asList( + new NumberConverter(), + new StringConverter(), + new BooleanConverter(), + new DateTimeConverter(), + new DurationConverter(), + new ExpressionNodeConverter(), + new ArrayConverter(), + new StructureConverter()); + + public EvaluationValue convertObject(Object object, ExpressionConfiguration configuration) { + + if (object == null) { + return EvaluationValue.nullValue(); + } + + if (object instanceof EvaluationValue) { + return (EvaluationValue) object; + } + + for (ConverterIfc converter : converters) { + if (converter.canConvert(object)) { + return converter.convert(object, configuration); + } + } + + throw new IllegalArgumentException( + "Unsupported data type '" + object.getClass().getName() + "'"); + } +} diff --git a/src/main/java/com/ezylang/evalex/data/EvaluationValue.java b/src/main/java/com/ezylang/evalex/data/EvaluationValue.java index b3d5307..fd21a29 100644 --- a/src/main/java/com/ezylang/evalex/data/EvaluationValue.java +++ b/src/main/java/com/ezylang/evalex/data/EvaluationValue.java @@ -15,13 +15,17 @@ */ package com.ezylang.evalex.data; +import com.ezylang.evalex.config.ExpressionConfiguration; import com.ezylang.evalex.parser.ASTNode; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; -import java.time.*; -import java.util.*; -import java.util.Map.Entry; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.Map; import lombok.Value; /** @@ -74,147 +78,146 @@ public class EvaluationValue implements Comparable { DataType dataType; /** - * Creates a new evaluation value by taking a good guess on the provided Java class and converting - * it to one of the supported types. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Input typeStorage Type
BigDecimalBigDecimal
Long, longBigDecimal
Integer, intBigDecimal
Short, shortBigDecimal
Byte, byteBigDecimal
Double, doubleBigDecimal *
Float, floatBigDecimal *
CharSequence , StringString
Boolean, booleanBoolean
Instant, instantInstant
ZonedDateTime, zonedDateTimeInstant
LocalDate, localDateInstant
OffsetDateTime, offsetDateTimeInstant
Duration, durationDuration
ASTNodeASTNode
List<?>List<EvaluationValue> - each entry will be converted
Map<?,?>Map<String><EvaluationValue> - each entry will be converted.
- * - * * Be careful with conversion problems when using float or double, which are fractional - * numbers. A (float)0.1 is e.g. converted to 0.10000000149011612 + * Creates a new evaluation value by using the default converter and configuration. * - * @param value One of the supported data types. + * @param value Any object that the default converter can convert. * @throws IllegalArgumentException if the data type can't be mapped. + * @see DefaultEvaluationValueConverter + * @deprecated Use {@link EvaluationValue(Object, ExpressionConfiguration)} instead. */ + @Deprecated(since = "3.1.0", forRemoval = true) public EvaluationValue(Object value) { - BigDecimal number = convertToBigDecimal(value); - if (number != null) { - this.dataType = DataType.NUMBER; - this.value = number; - } else if (value == null) { - this.dataType = DataType.NULL; - this.value = null; - } else if (value instanceof CharSequence) { - this.dataType = DataType.STRING; - this.value = ((CharSequence) value).toString(); - } else if (value instanceof Character) { - this.dataType = DataType.STRING; - this.value = ((Character) value).toString(); - } else if (value instanceof Boolean) { - this.dataType = DataType.BOOLEAN; - this.value = value; - } else if (value instanceof Instant) { - this.dataType = DataType.DATE_TIME; - this.value = value; - } else if (value instanceof ZonedDateTime) { - this.dataType = DataType.DATE_TIME; - this.value = ((ZonedDateTime) value).toInstant(); - } else if (value instanceof OffsetDateTime) { - this.dataType = DataType.DATE_TIME; - this.value = ((OffsetDateTime) value).toInstant(); - } else if (value instanceof LocalDate) { - this.dataType = DataType.DATE_TIME; - this.value = ((LocalDate) value).atStartOfDay().atOffset(ZoneOffset.UTC).toInstant(); - } else if (value instanceof Duration) { - this.dataType = DataType.DURATION; - this.value = value; - } else if (value instanceof ASTNode) { - this.dataType = DataType.EXPRESSION_NODE; - this.value = value; - } else if (value instanceof List) { - this.dataType = DataType.ARRAY; - this.value = convertToList((List) value); - } else if (value instanceof Map) { - this.dataType = DataType.STRUCTURE; - this.value = convertMapStructure((Map) value); - } else if (value instanceof EvaluationValue) { - this.dataType = ((EvaluationValue) value).getDataType(); - this.value = ((EvaluationValue) value).getValue(); - } else { - throw new IllegalArgumentException( - "Unsupported data type '" + value.getClass().getName() + "'"); - } + this(value, ExpressionConfiguration.defaultConfiguration()); } - public EvaluationValue(double value, MathContext mathContext) { - this.dataType = DataType.NUMBER; - this.value = new BigDecimal(Double.toString(value), mathContext); + /** + * Creates a new evaluation value by using the configured converter and configuration. + * + * @param value One of the supported data types. + * @param configuration The expression configuration to use. + * @throws IllegalArgumentException if the data type can't be mapped. + * @see ExpressionConfiguration#getEvaluationValueConverter() + */ + public EvaluationValue(Object value, ExpressionConfiguration configuration) { + + EvaluationValue converted = + configuration.getEvaluationValueConverter().convertObject(value, configuration); + + this.value = converted.getValue(); + this.dataType = converted.getDataType(); } - public EvaluationValue(LocalDateTime value, ZoneId zoneId) { - this.dataType = DataType.DATE_TIME; - this.value = value.atZone(zoneId).toInstant(); + /** + * Private constructor to directly create an instance with a given type and value. + * + * @param value The value to set, no conversion will be done. + * @param dataType The data type to set. + */ + private EvaluationValue(Object value, DataType dataType) { + this.dataType = dataType; + this.value = value; } /** - * Converts a {@link Map} of objects to a {@link Map} of {@link EvaluationValue} values. + * Creates a new null value. * - * @return A {@link Map} of {@link EvaluationValue} values. + * @return A new null value. */ - private Map convertMapStructure(Map value) { - Map structure = new HashMap<>(); - for (Entry entry : value.entrySet()) { - String name = entry.getKey().toString(); - structure.put(name, new EvaluationValue(entry.getValue())); - } - return structure; + public static EvaluationValue nullValue() { + return new EvaluationValue(null, DataType.NULL); } /** - * Converts a {@link List} of objects to a {@link List} of {@link EvaluationValue} values. + * Creates a new number value. * - * @return A {@link List} of {@link EvaluationValue} values. + * @param value The BigDecimal value to use. + * @return the new number value. */ - private List convertToList(List value) { - List array = new ArrayList<>(); - value.forEach(element -> array.add(new EvaluationValue(element))); - return array; + public static EvaluationValue numberValue(BigDecimal value) { + return new EvaluationValue(value, DataType.NUMBER); } /** - * Check and convert, if an {@link Object} can be converted to a {@link BigDecimal} value. + * Creates a new string value. * - * @return A {@link BigDecimal} value of the object, or null if it can't be - * converted. + * @param value The String value to use. + * @return the new string value. */ - private BigDecimal convertToBigDecimal(Object value) { - if (value instanceof BigDecimal) { - return (BigDecimal) value; - } else if (value instanceof Double) { - return BigDecimal.valueOf((double) value); - } else if (value instanceof Float) { - return BigDecimal.valueOf((float) value); - } else if (value instanceof Integer) { - return BigDecimal.valueOf((int) value); - } else if (value instanceof Long) { - return BigDecimal.valueOf((long) value); - } else if (value instanceof Short) { - return BigDecimal.valueOf((short) value); - } else if (value instanceof Byte) { - return BigDecimal.valueOf((byte) value); - } else { - return null; - } + public static EvaluationValue stringValue(String value) { + return new EvaluationValue(value, DataType.STRING); + } + + /** + * Creates a new boolean value. + * + * @param value The Boolean value to use. + * @return the new boolean value. + */ + public static EvaluationValue booleanValue(Boolean value) { + return new EvaluationValue(value, DataType.BOOLEAN); + } + + /** + * Creates a new date-time value. + * + * @param value The Instant value to use. + * @return the new date-time value. + */ + public static EvaluationValue dateTimeValue(Instant value) { + return new EvaluationValue(value, DataType.DATE_TIME); + } + + /** + * Creates a new duration value. + * + * @param value The Duration value to use. + * @return the new duration value. + */ + public static EvaluationValue durationValue(Duration value) { + return new EvaluationValue(value, DataType.DURATION); + } + + /** + * Creates a new expression node value. + * + * @param value The ASTNode value to use. + * @return the new expression node value. + */ + public static EvaluationValue expressionNodeValue(ASTNode value) { + return new EvaluationValue(value, DataType.EXPRESSION_NODE); + } + + /** + * Creates a new array value. + * + * @param value The List value to use. + * @return the new array value. + */ + public static EvaluationValue arrayValue(List value) { + return new EvaluationValue(value, DataType.ARRAY); + } + + /** + * Creates a new structure value. + * + * @param value The Map value to use. + * @return the new structure value. + */ + public static EvaluationValue structureValue(Map value) { + return new EvaluationValue(value, DataType.STRUCTURE); + } + + /** + * Creates a new evaluation value from a double value using the specified {@link MathContext}. + * + * @param value The double value. + * @param mathContext The math context to use. + * @deprecated since 3.1.0 - Use {@link EvaluationValue(Object, ExpressionConfiguration)}. + */ + @Deprecated(since = "3.1.0", forRemoval = true) + public EvaluationValue(double value, MathContext mathContext) { + this.dataType = DataType.NUMBER; + this.value = new BigDecimal(Double.toString(value), mathContext); } /** @@ -301,9 +304,9 @@ public class EvaluationValue implements Comparable { public static EvaluationValue numberOfString(String value, MathContext mathContext) { if (value.startsWith("0x") || value.startsWith("0X")) { BigInteger hexToInteger = new BigInteger(value.substring(2), 16); - return new EvaluationValue(new BigDecimal(hexToInteger, mathContext)); + return EvaluationValue.numberValue(new BigDecimal(hexToInteger, mathContext)); } else { - return new EvaluationValue(new BigDecimal(value, mathContext)); + return EvaluationValue.numberValue(new BigDecimal(value, mathContext)); } } diff --git a/src/main/java/com/ezylang/evalex/data/EvaluationValueConverterIfc.java b/src/main/java/com/ezylang/evalex/data/EvaluationValueConverterIfc.java new file mode 100644 index 0000000..b671860 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/EvaluationValueConverterIfc.java @@ -0,0 +1,35 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data; + +import com.ezylang.evalex.config.ExpressionConfiguration; + +/** + * Converter interface to be implemented by configurable evaluation value converters. Converts an + * arbitrary object to an {@link EvaluationValue}, using the specified configuration. + */ +public interface EvaluationValueConverterIfc { + + /** + * Called whenever an object has to be converted to an {@link EvaluationValue}. + * + * @param object The object holding the value. + * @param configuration The configuration to use. + * @return The converted {@link EvaluationValue}. + * @throws IllegalArgumentException if the object can't be converted. + */ + EvaluationValue convertObject(Object object, ExpressionConfiguration configuration); +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/ArrayConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/ArrayConverter.java new file mode 100644 index 0000000..016f2ed --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/ArrayConverter.java @@ -0,0 +1,37 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.util.ArrayList; +import java.util.List; + +/** Converter to convert to the ARRAY data type. */ +public class ArrayConverter implements ConverterIfc { + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + List array = new ArrayList<>(); + ((List) object).forEach(element -> array.add(new EvaluationValue(element, configuration))); + + return EvaluationValue.arrayValue(array); + } + + @Override + public boolean canConvert(Object object) { + return object instanceof List; + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/BooleanConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/BooleanConverter.java new file mode 100644 index 0000000..143f8cd --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/BooleanConverter.java @@ -0,0 +1,32 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; + +/** Converter to convert to the BOOLEAN data type. */ +public class BooleanConverter implements ConverterIfc { + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + return EvaluationValue.booleanValue((Boolean) object); + } + + @Override + public boolean canConvert(Object object) { + return object instanceof Boolean; + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/ConverterIfc.java b/src/main/java/com/ezylang/evalex/data/conversion/ConverterIfc.java new file mode 100644 index 0000000..6b01b17 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/ConverterIfc.java @@ -0,0 +1,47 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; + +/** + * Converter interface used by the {@link com.ezylang.evalex.data.DefaultEvaluationValueConverter}. + */ +public interface ConverterIfc { + + /** + * Called to convert a previously checked data type. + * + * @param object The object to convert. + * @param configuration The current expression configuration. + * @return The converted value. + */ + EvaluationValue convert(Object object, ExpressionConfiguration configuration); + + /** + * Checks, if a given object can be converted by this converter. + * + * @param object The object to convert. + * @return true if the object can be converted, false otherwise. + */ + boolean canConvert(Object object); + + default IllegalArgumentException illegalArgument(Object object) { + return new IllegalArgumentException( + "Unsupported data type '" + object.getClass().getName() + "'"); + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/DateTimeConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/DateTimeConverter.java new file mode 100644 index 0000000..95278db --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/DateTimeConverter.java @@ -0,0 +1,62 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.time.*; +import java.util.Calendar; +import java.util.Date; + +/** Converter to convert to the DATE_TIME data type. */ +public class DateTimeConverter implements ConverterIfc { + + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + + Instant instant; + + if (object instanceof Instant) { + instant = (Instant) object; + } else if (object instanceof ZonedDateTime) { + instant = ((ZonedDateTime) object).toInstant(); + } else if (object instanceof OffsetDateTime) { + instant = ((OffsetDateTime) object).toInstant(); + } else if (object instanceof LocalDate) { + instant = ((LocalDate) object).atStartOfDay().atZone(configuration.getZoneId()).toInstant(); + } else if (object instanceof LocalDateTime) { + instant = ((LocalDateTime) object).atZone(configuration.getZoneId()).toInstant(); + } else if (object instanceof Date) { + instant = ((Date) object).toInstant(); + } else if (object instanceof Calendar) { + instant = ((Calendar) object).toInstant(); + } else { + throw illegalArgument(object); + } + return EvaluationValue.dateTimeValue(instant); + } + + @Override + public boolean canConvert(Object object) { + return (object instanceof Instant + || object instanceof ZonedDateTime + || object instanceof OffsetDateTime + || object instanceof LocalDate + || object instanceof LocalDateTime + || object instanceof Date + || object instanceof Calendar); + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/DurationConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/DurationConverter.java new file mode 100644 index 0000000..43a95b5 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/DurationConverter.java @@ -0,0 +1,33 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.time.Duration; + +/** Converter to convert to the DURATION data type. */ +public class DurationConverter implements ConverterIfc { + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + return EvaluationValue.durationValue((Duration) object); + } + + @Override + public boolean canConvert(Object object) { + return object instanceof Duration; + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverter.java new file mode 100644 index 0000000..9222819 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverter.java @@ -0,0 +1,33 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.parser.ASTNode; + +/** Converter to convert to the EXPRESSION_NODE data type. */ +public class ExpressionNodeConverter implements ConverterIfc { + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + return EvaluationValue.expressionNodeValue((ASTNode) object); + } + + @Override + public boolean canConvert(Object object) { + return object instanceof ASTNode; + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/NumberConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/NumberConverter.java new file mode 100644 index 0000000..e73d51a --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/NumberConverter.java @@ -0,0 +1,60 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; + +/** Converter to convert to the NUMBER data type. */ +public class NumberConverter implements ConverterIfc { + + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + BigDecimal bigDecimal; + + if (object instanceof BigDecimal) { + bigDecimal = (BigDecimal) object; + } else if (object instanceof Double) { + bigDecimal = new BigDecimal(Double.toString((double) object), configuration.getMathContext()); + } else if (object instanceof Float) { + bigDecimal = BigDecimal.valueOf((float) object); + } else if (object instanceof Integer) { + bigDecimal = BigDecimal.valueOf((int) object); + } else if (object instanceof Long) { + bigDecimal = BigDecimal.valueOf((long) object); + } else if (object instanceof Short) { + bigDecimal = BigDecimal.valueOf((short) object); + } else if (object instanceof Byte) { + bigDecimal = BigDecimal.valueOf((byte) object); + } else { + throw illegalArgument(object); + } + + return EvaluationValue.numberValue(bigDecimal); + } + + @Override + public boolean canConvert(Object object) { + return (object instanceof BigDecimal + || object instanceof Double + || object instanceof Float + || object instanceof Integer + || object instanceof Long + || object instanceof Short + || object instanceof Byte); + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/StringConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/StringConverter.java new file mode 100644 index 0000000..636a65b --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/StringConverter.java @@ -0,0 +1,43 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; + +/** Converter to convert to the STRING data type. */ +public class StringConverter implements ConverterIfc { + + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + String string; + + if (object instanceof CharSequence) { + string = ((CharSequence) object).toString(); + } else if (object instanceof Character) { + string = ((Character) object).toString(); + } else { + throw illegalArgument(object); + } + + return EvaluationValue.stringValue(string); + } + + @Override + public boolean canConvert(Object object) { + return (object instanceof CharSequence || object instanceof Character); + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/StructureConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/StructureConverter.java new file mode 100644 index 0000000..e8d0330 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/StructureConverter.java @@ -0,0 +1,39 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.util.HashMap; +import java.util.Map; + +/** Converter to convert to the STRUCTURE data type. */ +public class StructureConverter implements ConverterIfc { + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + Map structure = new HashMap<>(); + for (Map.Entry entry : ((Map) object).entrySet()) { + String name = entry.getKey().toString(); + structure.put(name, new EvaluationValue(entry.getValue(), configuration)); + } + return EvaluationValue.structureValue(structure); + } + + @Override + public boolean canConvert(Object object) { + return object instanceof Map; + } +} diff --git a/src/main/java/com/ezylang/evalex/functions/basic/AbsFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/AbsFunction.java index 18302ce..a1db7c5 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/AbsFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/AbsFunction.java @@ -29,7 +29,7 @@ public class AbsFunction extends AbstractFunction { public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { - return new EvaluationValue( + return expression.convertValue( parameterValues[0].getNumberValue().abs(expression.getConfiguration().getMathContext())); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/CeilingFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/CeilingFunction.java index 02db39c..ff87b36 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/CeilingFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/CeilingFunction.java @@ -31,6 +31,6 @@ public class CeilingFunction extends AbstractFunction { EvaluationValue value = parameterValues[0]; - return new EvaluationValue(value.getNumberValue().setScale(0, RoundingMode.CEILING)); + return expression.convertValue(value.getNumberValue().setScale(0, RoundingMode.CEILING)); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/FactFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/FactFunction.java index aff60a5..eb25349 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/FactFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/FactFunction.java @@ -37,6 +37,6 @@ public class FactFunction extends AbstractFunction { new BigDecimal(i, expression.getConfiguration().getMathContext()), expression.getConfiguration().getMathContext()); } - return new EvaluationValue(factorial); + return expression.convertValue(factorial); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/FloorFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/FloorFunction.java index edfde3e..65131c5 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/FloorFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/FloorFunction.java @@ -31,6 +31,6 @@ public class FloorFunction extends AbstractFunction { EvaluationValue value = parameterValues[0]; - return new EvaluationValue(value.getNumberValue().setScale(0, RoundingMode.FLOOR)); + return expression.convertValue(value.getNumberValue().setScale(0, RoundingMode.FLOOR)); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/MaxFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/MaxFunction.java index 3a87a6a..b4c5ee6 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/MaxFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/MaxFunction.java @@ -34,6 +34,6 @@ public class MaxFunction extends AbstractFunction { max = parameter.getNumberValue(); } } - return new EvaluationValue(max); + return expression.convertValue(max); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/MinFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/MinFunction.java index bbbb41e..8b13d4f 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/MinFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/MinFunction.java @@ -34,6 +34,6 @@ public class MinFunction extends AbstractFunction { min = parameter.getNumberValue(); } } - return new EvaluationValue(min); + return expression.convertValue(min); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/NotFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/NotFunction.java index 46bee3b..9206411 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/NotFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/NotFunction.java @@ -31,6 +31,6 @@ public class NotFunction extends AbstractFunction { boolean result = parameterValues[0].getBooleanValue(); - return new EvaluationValue(!result); + return expression.convertValue(!result); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/RoundFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/RoundFunction.java index 1d2c0e9..6970c40 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/RoundFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/RoundFunction.java @@ -35,7 +35,7 @@ public class RoundFunction extends AbstractFunction { EvaluationValue value = parameterValues[0]; EvaluationValue precision = parameterValues[1]; - return new EvaluationValue( + return expression.convertValue( value .getNumberValue() .setScale( diff --git a/src/main/java/com/ezylang/evalex/functions/basic/SqrtFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/SqrtFunction.java index ef91138..ffe4ecc 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/SqrtFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/SqrtFunction.java @@ -40,7 +40,7 @@ public class SqrtFunction extends AbstractFunction { MathContext mathContext = expression.getConfiguration().getMathContext(); if (x.compareTo(BigDecimal.ZERO) == 0) { - return new EvaluationValue(BigDecimal.ZERO); + return expression.convertValue(BigDecimal.ZERO); } BigInteger n = x.movePointRight(mathContext.getPrecision() << 1).toBigInteger(); @@ -56,6 +56,6 @@ public class SqrtFunction extends AbstractFunction { test = ix.subtract(ixPrev).abs(); } while (test.compareTo(BigInteger.ZERO) != 0 && test.compareTo(BigInteger.ONE) != 0); - return new EvaluationValue(new BigDecimal(ix, mathContext.getPrecision())); + return expression.convertValue(new BigDecimal(ix, mathContext.getPrecision())); } } diff --git a/src/main/java/com/ezylang/evalex/functions/basic/SumFunction.java b/src/main/java/com/ezylang/evalex/functions/basic/SumFunction.java index 9010c2d..38817b0 100644 --- a/src/main/java/com/ezylang/evalex/functions/basic/SumFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/basic/SumFunction.java @@ -32,6 +32,6 @@ public class SumFunction extends AbstractFunction { for (EvaluationValue parameter : parameterValues) { sum = sum.add(parameter.getNumberValue(), expression.getConfiguration().getMathContext()); } - return new EvaluationValue(sum); + return expression.convertValue(sum); } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/AbstractDateTimeParseFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/AbstractDateTimeParseFunction.java index 3a396ef..eefe27f 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/AbstractDateTimeParseFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/AbstractDateTimeParseFunction.java @@ -26,7 +26,7 @@ public abstract class AbstractDateTimeParseFunction extends AbstractFunction { @Override public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { - ZoneId zoneId = expression.getConfiguration().getDefaultZoneId(); + ZoneId zoneId = expression.getConfiguration().getZoneId(); Instant instant; if (parameterValues.length < 2) { @@ -35,7 +35,7 @@ public abstract class AbstractDateTimeParseFunction extends AbstractFunction { instant = parse(parameterValues[0].getStringValue(), parameterValues[1].getStringValue(), zoneId); } - return new EvaluationValue(instant); + return expression.convertValue(instant); } protected abstract Instant parse(String value, String format, ZoneId zoneId); diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java index 3596514..640a516 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFormatFunction.java @@ -29,7 +29,7 @@ public class DateTimeFormatFunction extends AbstractFunction { public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { String formatted; - ZoneId zoneId = expression.getConfiguration().getDefaultZoneId(); + ZoneId zoneId = expression.getConfiguration().getZoneId(); if (parameterValues.length < 2) { formatted = parameterValues[0].getDateTimeValue().atZone(zoneId).toLocalDateTime().toString(); } else { @@ -38,6 +38,6 @@ public class DateTimeFormatFunction extends AbstractFunction { formatted = parameterValues[0].getDateTimeValue().atZone(zoneId).toLocalDateTime().format(formatter); } - return new EvaluationValue(formatted); + return expression.convertValue(formatted); } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFromEpochFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFromEpochFunction.java index 6432156..a0465e4 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFromEpochFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFromEpochFunction.java @@ -29,6 +29,6 @@ public class DateTimeFromEpochFunction extends AbstractFunction { public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { BigDecimal millis = parameterValues[0].getNumberValue(); - return new EvaluationValue(Instant.ofEpochMilli(millis.longValue())); + return expression.convertValue(Instant.ofEpochMilli(millis.longValue())); } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFunction.java index 56e1e25..935a548 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeFunction.java @@ -36,8 +36,8 @@ public class DateTimeFunction extends AbstractFunction { int second = parameterValues.length >= 6 ? parameterValues[5].getNumberValue().intValue() : 0; int nanoOfs = parameterValues.length >= 7 ? parameterValues[6].getNumberValue().intValue() : 0; - ZoneId zoneId = expression.getConfiguration().getDefaultZoneId(); - return new EvaluationValue( + ZoneId zoneId = expression.getConfiguration().getZoneId(); + return expression.convertValue( LocalDateTime.of(year, month, day, hour, minute, second, nanoOfs) .atZone(zoneId) .toInstant()); diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeToEpochFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeToEpochFunction.java index 2ee4f2e..7728913 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeToEpochFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DateTimeToEpochFunction.java @@ -26,6 +26,6 @@ public class DateTimeToEpochFunction extends AbstractFunction { @Override public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { - return new EvaluationValue(parameterValues[0].getDateTimeValue().toEpochMilli()); + return expression.convertValue(parameterValues[0].getDateTimeValue().toEpochMilli()); } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromDaysFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromDaysFunction.java index 95fac80..fb27f18 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromDaysFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromDaysFunction.java @@ -29,6 +29,6 @@ public class DurationFromDaysFunction extends AbstractFunction { public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { BigDecimal days = parameterValues[0].getNumberValue(); - return new EvaluationValue(Duration.ofDays(days.longValue())); + return expression.convertValue(Duration.ofDays(days.longValue())); } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromMillisFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromMillisFunction.java index 2d544f7..c0ed23f 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromMillisFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DurationFromMillisFunction.java @@ -29,6 +29,6 @@ public class DurationFromMillisFunction extends AbstractFunction { public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { BigDecimal millis = parameterValues[0].getNumberValue(); - return new EvaluationValue(Duration.ofMillis(millis.longValue())); + return expression.convertValue(Duration.ofMillis(millis.longValue())); } } diff --git a/src/main/java/com/ezylang/evalex/functions/datetime/DurationParseFunction.java b/src/main/java/com/ezylang/evalex/functions/datetime/DurationParseFunction.java index c7dc271..a5fda50 100644 --- a/src/main/java/com/ezylang/evalex/functions/datetime/DurationParseFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/datetime/DurationParseFunction.java @@ -28,6 +28,6 @@ public class DurationParseFunction extends AbstractFunction { public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { String text = parameterValues[0].getStringValue(); - return new EvaluationValue(Duration.parse(text)); + return expression.convertValue(Duration.parse(text)); } } diff --git a/src/main/java/com/ezylang/evalex/functions/string/StringContains.java b/src/main/java/com/ezylang/evalex/functions/string/StringContains.java index 886ebf0..4ed4c5c 100644 --- a/src/main/java/com/ezylang/evalex/functions/string/StringContains.java +++ b/src/main/java/com/ezylang/evalex/functions/string/StringContains.java @@ -30,6 +30,6 @@ public class StringContains extends AbstractFunction { Expression expression, Token functionToken, EvaluationValue... parameterValues) { String string = parameterValues[0].getStringValue(); String substring = parameterValues[1].getStringValue(); - return new EvaluationValue(string.toUpperCase().contains(substring.toUpperCase())); + return expression.convertValue(string.toUpperCase().contains(substring.toUpperCase())); } } diff --git a/src/main/java/com/ezylang/evalex/functions/string/StringLowerFunction.java b/src/main/java/com/ezylang/evalex/functions/string/StringLowerFunction.java index a3c7c38..a39fb82 100644 --- a/src/main/java/com/ezylang/evalex/functions/string/StringLowerFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/string/StringLowerFunction.java @@ -27,6 +27,6 @@ public class StringLowerFunction extends AbstractFunction { @Override public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { - return new EvaluationValue(parameterValues[0].getStringValue().toLowerCase()); + return expression.convertValue(parameterValues[0].getStringValue().toLowerCase()); } } diff --git a/src/main/java/com/ezylang/evalex/functions/string/StringUpperFunction.java b/src/main/java/com/ezylang/evalex/functions/string/StringUpperFunction.java index efc223d..09fee04 100644 --- a/src/main/java/com/ezylang/evalex/functions/string/StringUpperFunction.java +++ b/src/main/java/com/ezylang/evalex/functions/string/StringUpperFunction.java @@ -27,6 +27,6 @@ public class StringUpperFunction extends AbstractFunction { @Override public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { - return new EvaluationValue(parameterValues[0].getStringValue().toUpperCase()); + return expression.convertValue(parameterValues[0].getStringValue().toUpperCase()); } } diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixDivisionOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixDivisionOperator.java index 68624c4..f7e6667 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixDivisionOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixDivisionOperator.java @@ -42,7 +42,7 @@ public class InfixDivisionOperator extends AbstractOperator { throw new EvaluationException(operatorToken, "Division by zero"); } - return new EvaluationValue( + return expression.convertValue( leftOperand .getNumberValue() .divide( diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java index 918d362..2352827 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMinusOperator.java @@ -37,26 +37,26 @@ public class InfixMinusOperator extends AbstractOperator { EvaluationValue rightOperand = operands[1]; if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand .getNumberValue() .subtract( rightOperand.getNumberValue(), expression.getConfiguration().getMathContext())); } else if (leftOperand.isDateTimeValue() && rightOperand.isDateTimeValue()) { - return new EvaluationValue( + return expression.convertValue( Duration.ofMillis( leftOperand.getDateTimeValue().toEpochMilli() - rightOperand.getDateTimeValue().toEpochMilli())); } else if (leftOperand.isDateTimeValue() && rightOperand.isDurationValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand.getDateTimeValue().minus(rightOperand.getDurationValue())); } else if (leftOperand.isDurationValue() && rightOperand.isDurationValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand.getDurationValue().minus(rightOperand.getDurationValue())); } else if (leftOperand.isDateTimeValue() && rightOperand.isNumberValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand .getDateTimeValue() .minus(Duration.ofMillis(rightOperand.getNumberValue().longValue()))); diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixModuloOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixModuloOperator.java index dcb9ff6..1c26368 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixModuloOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixModuloOperator.java @@ -42,7 +42,7 @@ public class InfixModuloOperator extends AbstractOperator { throw new EvaluationException(operatorToken, "Division by zero"); } - return new EvaluationValue( + return expression.convertValue( leftOperand .getNumberValue() .remainder( diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMultiplicationOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMultiplicationOperator.java index 5776c91..86787b5 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMultiplicationOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixMultiplicationOperator.java @@ -36,7 +36,7 @@ public class InfixMultiplicationOperator extends AbstractOperator { EvaluationValue rightOperand = operands[1]; if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand .getNumberValue() .multiply( diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java index bcc795f..b3a5961 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPlusOperator.java @@ -37,23 +37,23 @@ public class InfixPlusOperator extends AbstractOperator { EvaluationValue rightOperand = operands[1]; if (leftOperand.isNumberValue() && rightOperand.isNumberValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand .getNumberValue() .add(rightOperand.getNumberValue(), expression.getConfiguration().getMathContext())); } else if (leftOperand.isDateTimeValue() && rightOperand.isDurationValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand.getDateTimeValue().plus(rightOperand.getDurationValue())); } else if (leftOperand.isDurationValue() && rightOperand.isDurationValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand.getDurationValue().plus(rightOperand.getDurationValue())); } else if (leftOperand.isDateTimeValue() && rightOperand.isNumberValue()) { - return new EvaluationValue( + return expression.convertValue( leftOperand .getDateTimeValue() .plus(Duration.ofMillis(rightOperand.getNumberValue().longValue()))); } else { - return new EvaluationValue(leftOperand.getStringValue() + rightOperand.getStringValue()); + return expression.convertValue(leftOperand.getStringValue() + rightOperand.getStringValue()); } } } diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPowerOfOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPowerOfOperator.java index f5d4f51..c81e59d 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPowerOfOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/InfixPowerOfOperator.java @@ -66,7 +66,7 @@ public class InfixPowerOfOperator extends AbstractOperator { if (signOf2 == -1) { result = BigDecimal.ONE.divide(result, mathContext.getPrecision(), RoundingMode.HALF_UP); } - return new EvaluationValue(result); + return expression.convertValue(result); } else { throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken); } diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixMinusOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixMinusOperator.java index c82a968..1565a63 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixMinusOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixMinusOperator.java @@ -33,7 +33,7 @@ public class PrefixMinusOperator extends AbstractOperator { EvaluationValue operand = operands[0]; if (operand.isNumberValue()) { - return new EvaluationValue( + return expression.convertValue( operand.getNumberValue().negate(expression.getConfiguration().getMathContext())); } else { throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken); diff --git a/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixPlusOperator.java b/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixPlusOperator.java index 6b4436b..984a2cc 100644 --- a/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixPlusOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/arithmetic/PrefixPlusOperator.java @@ -33,7 +33,7 @@ public class PrefixPlusOperator extends AbstractOperator { EvaluationValue operator = operands[0]; if (operator.isNumberValue()) { - return new EvaluationValue( + return expression.convertValue( operator.getNumberValue().plus(expression.getConfiguration().getMathContext())); } else { throw EvaluationException.ofUnsupportedDataTypeInOperation(operatorToken); diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java index d3c96f0..489adf7 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java @@ -30,6 +30,6 @@ public class InfixAndOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(operands[0].getBooleanValue() && operands[1].getBooleanValue()); + return expression.convertValue(operands[0].getBooleanValue() && operands[1].getBooleanValue()); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperator.java index b373c18..6bef6d8 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixEqualsOperator.java @@ -30,6 +30,6 @@ public class InfixEqualsOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(operands[0].equals(operands[1])); + return expression.convertValue(operands[0].equals(operands[1])); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperator.java index d846842..7d871c8 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterEqualsOperator.java @@ -30,6 +30,6 @@ public class InfixGreaterEqualsOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(operands[0].compareTo(operands[1]) >= 0); + return expression.convertValue(operands[0].compareTo(operands[1]) >= 0); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperator.java index 431d957..afca50c 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixGreaterOperator.java @@ -30,6 +30,6 @@ public class InfixGreaterOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(operands[0].compareTo(operands[1]) > 0); + return expression.convertValue(operands[0].compareTo(operands[1]) > 0); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperator.java index 5fde2af..f7fa759 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessEqualsOperator.java @@ -30,6 +30,6 @@ public class InfixLessEqualsOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(operands[0].compareTo(operands[1]) <= 0); + return expression.convertValue(operands[0].compareTo(operands[1]) <= 0); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessOperator.java index 3234fc3..78e9a32 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixLessOperator.java @@ -30,6 +30,6 @@ public class InfixLessOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(operands[0].compareTo(operands[1]) < 0); + return expression.convertValue(operands[0].compareTo(operands[1]) < 0); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperator.java index 5f0d386..50fd130 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixNotEqualsOperator.java @@ -30,6 +30,6 @@ public class InfixNotEqualsOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(!operands[0].equals(operands[1])); + return expression.convertValue(!operands[0].equals(operands[1])); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java index dc4a8bc..f487287 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java @@ -30,6 +30,6 @@ public class InfixOrOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(operands[0].getBooleanValue() || operands[1].getBooleanValue()); + return expression.convertValue(operands[0].getBooleanValue() || operands[1].getBooleanValue()); } } diff --git a/src/main/java/com/ezylang/evalex/operators/booleans/PrefixNotOperator.java b/src/main/java/com/ezylang/evalex/operators/booleans/PrefixNotOperator.java index aa95851..597fabb 100644 --- a/src/main/java/com/ezylang/evalex/operators/booleans/PrefixNotOperator.java +++ b/src/main/java/com/ezylang/evalex/operators/booleans/PrefixNotOperator.java @@ -28,6 +28,6 @@ public class PrefixNotOperator extends AbstractOperator { @Override public EvaluationValue evaluate( Expression expression, Token operatorToken, EvaluationValue... operands) { - return new EvaluationValue(!operands[0].getBooleanValue()); + return expression.convertValue(!operands[0].getBooleanValue()); } } diff --git a/src/test/java/com/ezylang/evalex/config/TestConfigurationProvider.java b/src/test/java/com/ezylang/evalex/config/TestConfigurationProvider.java index 6be6313..7cdeb0b 100644 --- a/src/test/java/com/ezylang/evalex/config/TestConfigurationProvider.java +++ b/src/test/java/com/ezylang/evalex/config/TestConfigurationProvider.java @@ -42,7 +42,7 @@ public class TestConfigurationProvider { public EvaluationValue evaluate( Expression expression, Token functionToken, EvaluationValue... parameterValues) { // dummy implementation - return new EvaluationValue("OK"); + return expression.convertValue("OK"); } } diff --git a/src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java b/src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java new file mode 100644 index 0000000..0302e33 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java @@ -0,0 +1,52 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import org.junit.jupiter.api.Test; + +class DefaultEvaluationValueConverterTest { + + private final DefaultEvaluationValueConverter converter = new DefaultEvaluationValueConverter(); + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + @Test + void testNull() { + EvaluationValue converted = converter.convertObject(null, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NULL); + } + + @Test + void testNestedEvaluationValueNull() { + EvaluationValue converted = + converter.convertObject(EvaluationValue.stringValue("Hello"), defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.STRING); + assertThat(converted.getStringValue()).isEqualTo("Hello"); + } + + @Test + void testException() { + assertThatThrownBy(() -> converter.convertObject(new int[] {1, 2, 3}, defaultConfiguration)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported data type '[I'"); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java b/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java index bd0b06d..1c5d033 100644 --- a/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java +++ b/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.ezylang.evalex.EvaluationException; import com.ezylang.evalex.Expression; +import com.ezylang.evalex.config.ExpressionConfiguration; import com.ezylang.evalex.parser.ASTNode; import com.ezylang.evalex.parser.ParseException; import com.ezylang.evalex.parser.Token; @@ -131,34 +132,73 @@ class EvaluationValueTest { } @Test - void testLocalDate() { - LocalDate localDate = LocalDate.parse("2022-10-30"); - EvaluationValue value = new EvaluationValue(localDate); + void testLocalDateCETDaylightSavingTime() { + LocalDate localDate = LocalDate.parse("2022-10-20"); + EvaluationValue value = + new EvaluationValue( + localDate, ExpressionConfiguration.builder().zoneId(ZoneId.of("CET")).build()); + + assertThat(value.isDateTimeValue()).isTrue(); + assertDataIsCorrect( + value, + "2022-10-19T22:00:00Z", + BigDecimal.ZERO, + false, + localDate.atStartOfDay().atZone(ZoneId.of("CET")).toInstant(), + Duration.ZERO, + Instant.class); + } + + @Test + void testLocalDateCETNoDaylightSavingTime() { + LocalDate localDate = LocalDate.parse("2022-11-30"); + EvaluationValue value = + new EvaluationValue( + localDate, ExpressionConfiguration.builder().zoneId(ZoneId.of("CET")).build()); assertThat(value.isDateTimeValue()).isTrue(); assertDataIsCorrect( value, - "2022-10-30T00:00:00Z", + "2022-11-29T23:00:00Z", BigDecimal.ZERO, false, - Instant.parse("2022-10-30T00:00:00Z"), + localDate.atStartOfDay().atZone(ZoneId.of("CET")).toInstant(), Duration.ZERO, Instant.class); } @Test - void testLocalDateTime() { - ZoneId zoneId = ZoneId.of("UTC+2"); - LocalDateTime localDateTime = LocalDateTime.parse("2022-10-30T11:20:30"); - EvaluationValue value = new EvaluationValue(localDateTime, zoneId); + void testLocalDateTimeDaylightSavingTime() { + LocalDateTime localDateTime = LocalDateTime.parse("2022-10-20T11:20:30"); + EvaluationValue value = + new EvaluationValue( + localDateTime, ExpressionConfiguration.builder().zoneId(ZoneId.of("CET")).build()); + + assertThat(value.isDateTimeValue()).isTrue(); + assertDataIsCorrect( + value, + "2022-10-20T09:20:30Z", + BigDecimal.ZERO, + false, + localDateTime.atZone(ZoneId.of("CET")).toInstant(), + Duration.ZERO, + Instant.class); + } + + @Test + void testLocalDateTimeNoDaylightSavingTime() { + LocalDateTime localDateTime = LocalDateTime.parse("2022-11-20T11:20:30"); + EvaluationValue value = + new EvaluationValue( + localDateTime, ExpressionConfiguration.builder().zoneId(ZoneId.of("CET")).build()); assertThat(value.isDateTimeValue()).isTrue(); assertDataIsCorrect( value, - "2022-10-30T09:20:30Z", + "2022-11-20T10:20:30Z", BigDecimal.ZERO, false, - localDateTime.atZone(zoneId).toInstant(), + localDateTime.atZone(ZoneId.of("CET")).toInstant(), Duration.ZERO, Instant.class); } diff --git a/src/test/java/com/ezylang/evalex/data/conversion/ArrayConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/ArrayConverterTest.java new file mode 100644 index 0000000..5a378d3 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/ArrayConverterTest.java @@ -0,0 +1,75 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ArrayConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final ArrayConverter converter = new ArrayConverter(); + + @Test + void testArrayMixed() { + EvaluationValue value = + converter.convert( + Arrays.asList(new BigDecimal(1), new BigDecimal(2), "hello", null), + defaultConfiguration); + + assertThat(value.isArrayValue()).isTrue(); + assertThat(value.getArrayValue()).hasSize(4); + assertThat(value.getArrayValue().get(0).isNumberValue()).isTrue(); + assertThat(value.getArrayValue().get(0).getStringValue()).isEqualTo("1"); + assertThat(value.getArrayValue().get(1).isNumberValue()).isTrue(); + assertThat(value.getArrayValue().get(1).getStringValue()).isEqualTo("2"); + assertThat(value.getArrayValue().get(2).isStringValue()).isTrue(); + assertThat(value.getArrayValue().get(2).getStringValue()).isEqualTo("hello"); + assertThat(value.getArrayValue().get(3).isNullValue()).isTrue(); + assertThat(value.getArrayValue().get(3).getStringValue()).isNull(); + + assertThat(value.getValue()).isInstanceOf(List.class); + } + + @Test + void testArrayEmpty() { + EvaluationValue value = converter.convert(Collections.EMPTY_LIST, defaultConfiguration); + + assertThat(value.isArrayValue()).isTrue(); + assertThat(value.getArrayValue()).isEmpty(); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(Collections.EMPTY_LIST)).isTrue(); + assertThat(converter.canConvert(Arrays.asList(1, 2, 3))).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert(new String[] {"1", "2", "3"})).isFalse(); + assertThat(converter.canConvert(new BigDecimal(1))).isFalse(); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/conversion/BooleanConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/BooleanConverterTest.java new file mode 100644 index 0000000..4266b90 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/BooleanConverterTest.java @@ -0,0 +1,59 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + +class BooleanConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final BooleanConverter converter = new BooleanConverter(); + + @Test + void testBooleanTrue() { + EvaluationValue value = converter.convert(true, defaultConfiguration); + + assertThat(value.isBooleanValue()).isTrue(); + assertThat(value.getBooleanValue()).isTrue(); + } + + @Test + void testBooleanFalse() { + EvaluationValue value = converter.convert(false, defaultConfiguration); + + assertThat(value.isBooleanValue()).isTrue(); + assertThat(value.getBooleanValue()).isFalse(); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(true)).isTrue(); + assertThat(converter.canConvert(Boolean.valueOf("false"))).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert("true")).isFalse(); + assertThat(converter.canConvert(new BigDecimal(1))).isFalse(); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/conversion/DateTimeConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/DateTimeConverterTest.java new file mode 100644 index 0000000..b2d6830 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/DateTimeConverterTest.java @@ -0,0 +1,152 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; +import java.time.*; +import java.util.Calendar; +import java.util.Date; +import org.junit.jupiter.api.Test; + +class DateTimeConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final ExpressionConfiguration cetConfiguration = + ExpressionConfiguration.builder().zoneId(ZoneId.of("Europe/Berlin")).build(); + + private final DateTimeConverter converter = new DateTimeConverter(); + + @Test + void testInstant() { + Instant now = Instant.now(); + + EvaluationValue converted = converter.convert(now, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue()).isEqualTo(now); + } + + @Test + void testZonedDateTime() { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("US/Central")); + + EvaluationValue converted = converter.convert(now, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue()).isEqualTo(now.toInstant()); + } + + @Test + void testOffsetDateTime() { + OffsetDateTime now = OffsetDateTime.now(ZoneId.of("US/Central")); + + EvaluationValue converted = converter.convert(now, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue()).isEqualTo(now.toInstant()); + } + + @Test + void testLocalDateDaylightSaving() { + LocalDate localDate = LocalDate.parse("2022-10-20"); + + EvaluationValue converted = new EvaluationValue(localDate, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue().toString()).hasToString("2022-10-19T22:00:00Z"); + } + + @Test + void testLocalDateNoDaylightSaving() { + LocalDate localDate = LocalDate.parse("2022-11-20"); + + EvaluationValue converted = new EvaluationValue(localDate, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue().toString()).hasToString("2022-11-19T23:00:00Z"); + } + + @Test + void testLocalDateTimeDaylightSaving() { + LocalDateTime localDateTime = LocalDateTime.parse("2022-10-20T11:21:30"); + + EvaluationValue converted = new EvaluationValue(localDateTime, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue().toString()).hasToString("2022-10-20T09:21:30Z"); + } + + @Test + void testLocalDateTimeNoDaylightSaving() { + LocalDateTime localDateTime = LocalDateTime.parse("2022-11-20T11:21:30"); + + EvaluationValue converted = new EvaluationValue(localDateTime, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue().toString()).hasToString("2022-11-20T10:21:30Z"); + } + + @Test + void testDate() { + Date now = new Date(); + + EvaluationValue converted = converter.convert(now, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue()).isEqualTo(now.toInstant()); + } + + @Test + void testCalendar() { + Calendar now = Calendar.getInstance(); + + EvaluationValue converted = converter.convert(now, cetConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DATE_TIME); + assertThat(converted.getValue()).isEqualTo(now.toInstant()); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(Instant.now())).isTrue(); + assertThat(converter.canConvert(ZonedDateTime.now())).isTrue(); + assertThat(converter.canConvert(OffsetDateTime.now())).isTrue(); + assertThat(converter.canConvert(LocalDate.now())).isTrue(); + assertThat(converter.canConvert(LocalDateTime.now())).isTrue(); + assertThat(converter.canConvert(new Date())).isTrue(); + assertThat(converter.canConvert(Calendar.getInstance())).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert("hello")).isFalse(); + assertThat(converter.canConvert(new BigDecimal(1))).isFalse(); + } + + @Test + void testException() { + assertThatThrownBy(() -> converter.convert("hello", defaultConfiguration)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported data type 'java.lang.String'"); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/conversion/DuratinCoverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/DuratinCoverterTest.java new file mode 100644 index 0000000..128cd75 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/DuratinCoverterTest.java @@ -0,0 +1,53 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; +import java.time.Duration; +import org.junit.jupiter.api.Test; + +class DuratinCoverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final DurationConverter converter = new DurationConverter(); + + @Test + void testDuration() { + Duration duration = Duration.ofMinutes(5); + + EvaluationValue converted = converter.convert(duration, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.DURATION); + assertThat(converted.getValue()).isEqualTo(duration); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(Duration.ofMinutes(10))).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert("hello")).isFalse(); + assertThat(converter.canConvert(new BigDecimal(10))).isFalse(); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverterTest.java new file mode 100644 index 0000000..276fb53 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverterTest.java @@ -0,0 +1,56 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.parser.ASTNode; +import com.ezylang.evalex.parser.Token; +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + +class ExpressionNodeConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final ExpressionNodeConverter converter = new ExpressionNodeConverter(); + + private final ASTNode testNode = + new ASTNode(new Token(1, "a", Token.TokenType.VARIABLE_OR_CONSTANT)); + + @Test + void testDuration() { + + EvaluationValue converted = converter.convert(testNode, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.EXPRESSION_NODE); + assertThat(converted.getExpressionNode().toJSON()).isEqualTo(testNode.toJSON()); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(testNode)).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert("hello")).isFalse(); + assertThat(converter.canConvert(new BigDecimal(10))).isFalse(); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/conversion/NumberConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/NumberConverterTest.java new file mode 100644 index 0000000..58f5496 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/NumberConverterTest.java @@ -0,0 +1,122 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + +class NumberConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final NumberConverter converter = new NumberConverter(); + + @Test + void testBigDecimal() { + BigDecimal value = new BigDecimal("23"); + + EvaluationValue converted = converter.convert(value, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NUMBER); + assertThat(converted.getValue()).isEqualTo(value); + } + + @Test + void testDouble() { + double value = Double.parseDouble("2.5"); + + EvaluationValue converted = converter.convert(value, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NUMBER); + assertThat(converted.getNumberValue().toPlainString()).isEqualTo("2.5"); + } + + @Test + void testFloat() { + double value = Float.parseFloat("3.5"); + + EvaluationValue converted = converter.convert(value, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NUMBER); + assertThat(converted.getNumberValue().toPlainString()).isEqualTo("3.5"); + } + + @Test + void testInteger() { + EvaluationValue converted = converter.convert(5, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NUMBER); + assertThat(converted.getNumberValue().toPlainString()).isEqualTo("5"); + } + + @Test + void testLong() { + EvaluationValue converted = converter.convert(949345345343345673L, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NUMBER); + assertThat(converted.getNumberValue().toPlainString()).isEqualTo("949345345343345673"); + } + + @Test + void testShort() { + double value = Short.parseShort("7"); + + EvaluationValue converted = converter.convert(value, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NUMBER); + assertThat(converted.getNumberValue().toPlainString()).isEqualTo("7.0"); + } + + @Test + void testByte() { + double value = Byte.parseByte("4"); + + EvaluationValue converted = converter.convert(value, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.NUMBER); + assertThat(converted.getNumberValue().toPlainString()).isEqualTo("4.0"); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(new BigDecimal(8))).isTrue(); + assertThat(converter.canConvert(Double.parseDouble("3.0"))).isTrue(); + assertThat(converter.canConvert(Float.parseFloat("2.0"))).isTrue(); + assertThat(converter.canConvert(3)).isTrue(); + assertThat(converter.canConvert(3L)).isTrue(); + assertThat(converter.canConvert(Short.parseShort("79"))).isTrue(); + assertThat(converter.canConvert(Byte.parseByte("2"))).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert("hello")).isFalse(); + assertThat(converter.canConvert(true)).isFalse(); + } + + @Test + void testException() { + assertThatThrownBy(() -> converter.convert("not a number", defaultConfiguration)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported data type 'java.lang.String'"); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/conversion/StringConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/StringConverterTest.java new file mode 100644 index 0000000..12088d9 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/StringConverterTest.java @@ -0,0 +1,67 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + +class StringConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final StringConverter converter = new StringConverter(); + + @Test + void testString() { + EvaluationValue converted = converter.convert("Hello World!", defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.STRING); + assertThat(converted.getValue()).isEqualTo("Hello World!"); + } + + @Test + void testCharacter() { + EvaluationValue converted = converter.convert('P', defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.STRING); + assertThat(converted.getValue()).isEqualTo("P"); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert("Hello")).isTrue(); + assertThat(converter.canConvert('P')).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert(new BigDecimal(3))).isFalse(); + assertThat(converter.canConvert(true)).isFalse(); + } + + @Test + void testException() { + assertThatThrownBy(() -> converter.convert(7, defaultConfiguration)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported data type 'java.lang.Integer'"); + } +} diff --git a/src/test/java/com/ezylang/evalex/data/conversion/StructureConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/StructureConverterTest.java new file mode 100644 index 0000000..e78f1b1 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/StructureConverterTest.java @@ -0,0 +1,77 @@ +/* + Copyright 2012-2023 Udo Klimaschewski + + Licensed 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 + + http://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 com.ezylang.evalex.data.conversion; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.data.EvaluationValue; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class StructureConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final StructureConverter converter = new StructureConverter(); + + @Test + void testStructureMixed() { + Map testMap = new HashMap<>(); + + testMap.put("key1", "value1"); + testMap.put("key2", 4); + testMap.put("key3", true); + + EvaluationValue converted = converter.convert(testMap, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.STRUCTURE); + assertThat(converted.getStructureValue()).hasSize(3); + + assertThat(converted.getStructureValue().get("key1").isStringValue()).isTrue(); + assertThat(converted.getStructureValue().get("key1").getStringValue()).isEqualTo("value1"); + + assertThat(converted.getStructureValue().get("key2").isNumberValue()).isTrue(); + assertThat(converted.getStructureValue().get("key2").getNumberValue()) + .isEqualTo(new BigDecimal("4")); + + assertThat(converted.getStructureValue().get("key3").isBooleanValue()).isTrue(); + assertThat(converted.getStructureValue().get("key3").getBooleanValue()).isTrue(); + } + + @Test + void testMapEmpty() { + EvaluationValue value = converter.convert(Collections.EMPTY_MAP, defaultConfiguration); + + assertThat(value.isStructureValue()).isTrue(); + assertThat(value.getStructureValue()).isEmpty(); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(Collections.EMPTY_MAP)).isTrue(); + } + + @Test + void testCanNotConvert() { + assertThat(converter.canConvert(new int[] {1, 2, 3})).isFalse(); + assertThat(converter.canConvert(new BigDecimal(1))).isFalse(); + } +} diff --git a/src/test/java/com/ezylang/evalex/functions/basic/BasicFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/basic/BasicFunctionsTest.java index b1e06ac..683a132 100644 --- a/src/test/java/com/ezylang/evalex/functions/basic/BasicFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/basic/BasicFunctionsTest.java @@ -187,16 +187,19 @@ class BasicFunctionsTest extends BaseEvaluationTest { // somehow, code coverage for the NotFunction traditional tests does not work on Google build NotFunction notFunction = new NotFunction(); Expression expressionMock = Mockito.mock(Expression.class); + Mockito.when(expressionMock.convertValue(true)).thenReturn(EvaluationValue.booleanValue(true)); + Mockito.when(expressionMock.convertValue(false)) + .thenReturn(EvaluationValue.booleanValue(false)); Token token = new Token(1, "NOT", TokenType.FUNCTION, notFunction); assertThat( notFunction - .evaluate(expressionMock, token, new EvaluationValue(true)) + .evaluate(expressionMock, token, EvaluationValue.booleanValue(true)) .getBooleanValue()) .isFalse(); assertThat( notFunction - .evaluate(expressionMock, token, new EvaluationValue(false)) + .evaluate(expressionMock, token, EvaluationValue.booleanValue(false)) .getBooleanValue()) .isTrue(); } diff --git a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java index 0844f63..521d544 100644 --- a/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/datetime/DateTimeFunctionsTest.java @@ -28,7 +28,7 @@ class DateTimeFunctionsTest extends BaseEvaluationTest { private static final ExpressionConfiguration DateTimeTestConfiguration = TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder() - .defaultZoneId(ZoneId.of("UTC+2")) + .zoneId(ZoneId.of("UTC+2")) .build(); @ParameterizedTest diff --git a/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java b/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java index ad1fe42..3a8db50 100644 --- a/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java +++ b/src/test/java/com/ezylang/evalex/operators/arithmetic/ArithmeticOperatorsTest.java @@ -106,7 +106,7 @@ class ArithmeticOperatorsTest extends BaseEvaluationTest { expression, expectedResult, TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder() - .defaultZoneId(ZoneId.of("UTC+2")) + .zoneId(ZoneId.of("UTC+2")) .build()); } @@ -200,7 +200,7 @@ class ArithmeticOperatorsTest extends BaseEvaluationTest { expression, expectedResult, TestConfigurationProvider.StandardConfigurationWithAdditionalTestOperators.toBuilder() - .defaultZoneId(ZoneId.of("UTC+2")) + .zoneId(ZoneId.of("UTC+2")) .build()); } -- 2.51.2