diff --git a/docs/concepts/datatypes.md b/docs/concepts/datatypes.md index 077df167..45e17208 100644 --- a/docs/concepts/datatypes.md +++ b/docs/concepts/datatypes.md @@ -19,8 +19,11 @@ EvalEx supports the following data types: | ARRAY | java.util.List | | STRUCTURE | java.util.Map | | EXPRESSION_NODE | com.ezylang.evalex.parser.ASTNode | +| BINARY[^1] | java.lang.Object | | NULL | null | +[^1]: Since 3.3.0 + Data is stored in an _EvaluationValue_, which holds the value and the data type. ### NUMBER @@ -214,6 +217,18 @@ Note that the above expression is not evaluated as "2 * 4 + 3", which would resu Instead, the sub-expression "4 + 3" is calculated first, when it comes to finding the value of the variable _b_. Resulting in calculation of "2 * 7", which is 14. + +### BINARY + +A representation for an undefined (raw), non-null object that could not fit in any of the previous +data types. + +This allows for special functions to handle any object type. + +The binary data type is **disabled** by default and can be enabled by setting a dedicated property +in the [Configuration](../configuration/configuration.html). + + ### NULL A representation for _null_ objects. diff --git a/docs/concepts/parsing_evaluation.md b/docs/concepts/parsing_evaluation.md index c9bbf7a2..b09209e3 100644 --- a/docs/concepts/parsing_evaluation.md +++ b/docs/concepts/parsing_evaluation.md @@ -56,8 +56,11 @@ _EvaluationValue_ will be of one of the types: - NUMBER - If the expression resulted in a number. - STRING - If the expression resulted in a string. - BOOLEAN - If the expression resulted in a boolean value. +- DATE_TIME - If the expression resulted in a date/time value. +- DURATION - If the expression resulted in a duration value. - ARRAY - If the expression resulted in an array. - STRUCTURE - If the expression resulted in a structure. +- BINARY - If the expression could not be converted to any of the previous types. The _EvaluationValue_ has methods to check and retrieve/convert the evaluation value. diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index d3baf2ac..9e16664c 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -27,6 +27,7 @@ ExpressionConfiguration configuration=ExpressionConfiguration.builder() .powerOfPrecedence(OperatorIfc.OPERATOR_PRECEDENCE_POWER) .stripTrailingZeros(true) .structuresAllowed(true) + .binaryAllowed(false) .singleQuoteStringLiteralsAllowed(false) .zoneId(ZoneId.systemDefault()) .build(); @@ -45,6 +46,15 @@ Specifies if the array index function is allowed (default is true). If set to fa will throw a _ParseException_, if there is a '[' is encountered in the expression and also no operator or function is defined for this character. +### Binary allowed + +Specifies if the binary[^1] (raw) data type is allowed for expressions that can not be converted to any +known data type. + +See chapter [Data Types](../concepts/datatypes.html) for details. + +[^1]: Since 3.3.0 + ### Data Accessor The Data Accessor is responsible for storing and retrieving variable values. diff --git a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java index 3abf2d23..d8ca3da3 100644 --- a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java +++ b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java @@ -224,6 +224,13 @@ public class ExpressionConfiguration { /** Support for structures in expressions are allowed or not. */ @Builder.Default private final boolean structuresAllowed = true; + /** + * Support for the binary (undefined) data type is allowed or not. + * + * @since 3.3.0 + */ + @Builder.Default private final boolean binaryAllowed = false; + /** Support for implicit multiplication, like in (a+b)(b+c) are allowed or not. */ @Builder.Default private final boolean implicitMultiplicationAllowed = true; diff --git a/src/main/java/com/ezylang/evalex/data/EvaluationValue.java b/src/main/java/com/ezylang/evalex/data/EvaluationValue.java index 7f5c658c..2ed4b56f 100644 --- a/src/main/java/com/ezylang/evalex/data/EvaluationValue.java +++ b/src/main/java/com/ezylang/evalex/data/EvaluationValue.java @@ -79,7 +79,9 @@ public enum DataType { */ EXPRESSION_NODE, /** A null value */ - NULL + NULL, + /** Raw (undefined) type, stored as an {@link Object}. */ + BINARY } Object value; @@ -219,6 +221,17 @@ public static EvaluationValue structureValue(Map value) { return new EvaluationValue(value, DataType.STRUCTURE); } + /** + * Creates a new binary (raw) value. + * + * @param value The Object to use. + * @return the new binary value. + * @since 3.3.0 + */ + public static EvaluationValue binaryValue(Object value) { + return new EvaluationValue(value, DataType.BINARY); + } + /** * Checks if the value is of type {@link DataType#NUMBER}. * @@ -295,6 +308,16 @@ public boolean isNullValue() { return getDataType() == DataType.NULL; } + /** + * Checks if the value is of type {@link DataType#BINARY}. + * + * @return true or false. + * @since 3.3.0 + */ + public boolean isBinaryValue() { + return getDataType() == DataType.BINARY; + } + /** * Creates a {@link DataType#NUMBER} value from a {@link String}. * diff --git a/src/main/java/com/ezylang/evalex/data/conversion/BinaryConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/BinaryConverter.java new file mode 100644 index 00000000..0952a2ef --- /dev/null +++ b/src/main/java/com/ezylang/evalex/data/conversion/BinaryConverter.java @@ -0,0 +1,37 @@ +/* + Copyright 2012-2024 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 BINARY data type. + * + * @author oswaldobapvicjr + * @since 3.3.0 + */ +public class BinaryConverter implements ConverterIfc { + @Override + public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { + return EvaluationValue.binaryValue(object); + } + + @Override + public boolean canConvert(Object object) { + return true; + } +} diff --git a/src/main/java/com/ezylang/evalex/data/conversion/DefaultEvaluationValueConverter.java b/src/main/java/com/ezylang/evalex/data/conversion/DefaultEvaluationValueConverter.java index a2aa27bd..3fd6a0ba 100644 --- a/src/main/java/com/ezylang/evalex/data/conversion/DefaultEvaluationValueConverter.java +++ b/src/main/java/com/ezylang/evalex/data/conversion/DefaultEvaluationValueConverter.java @@ -83,6 +83,10 @@ public EvaluationValue convertObject(Object object, ExpressionConfiguration conf } } + if (configuration.isBinaryAllowed()) { + return EvaluationValue.binaryValue(object); + } + throw new IllegalArgumentException( "Unsupported data type '" + object.getClass().getName() + "'"); } diff --git a/src/test/java/com/ezylang/evalex/config/ExpressionConfigurationTest.java b/src/test/java/com/ezylang/evalex/config/ExpressionConfigurationTest.java index b787c81f..9c39ea29 100644 --- a/src/test/java/com/ezylang/evalex/config/ExpressionConfigurationTest.java +++ b/src/test/java/com/ezylang/evalex/config/ExpressionConfigurationTest.java @@ -159,6 +159,14 @@ void testStructuresAllowed() { assertThat(configuration.isStructuresAllowed()).isFalse(); } + @Test + void testBinaryAllowed() { + ExpressionConfiguration configuration = + ExpressionConfiguration.builder().binaryAllowed(true).build(); + + assertThat(configuration.isArraysAllowed()).isTrue(); + } + @Test void testSingleQuoteStringLiteralsAllowed() { ExpressionConfiguration configuration = diff --git a/src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java b/src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java index 5d77a3a7..7b7695b0 100644 --- a/src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java +++ b/src/test/java/com/ezylang/evalex/data/DefaultEvaluationValueConverterTest.java @@ -45,9 +45,23 @@ void testNestedEvaluationValueNull() { } @Test - void testException() { + void testDefaultEvaluationWithBinaryAllowed() { + ExpressionConfiguration configuration = + ExpressionConfiguration.builder().binaryAllowed(true).build(); + final Object rawValue = new Object(); + EvaluationValue converted = converter.convertObject(rawValue, configuration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.BINARY); + assertThat(converted.isBinaryValue()).isTrue(); + assertThat(converted.getValue()).isSameAs(rawValue); + } + + @Test + void testExceptionWithBinaryNotAllowed() { + ExpressionConfiguration configuration = + ExpressionConfiguration.builder().binaryAllowed(false).build(); final Error error = new Error(); - assertThatThrownBy(() -> converter.convertObject(error, defaultConfiguration)) + assertThatThrownBy(() -> converter.convertObject(error, configuration)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported data type 'java.lang.Error'"); } diff --git a/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java b/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java index 9cfc8b36..b6841ef0 100644 --- a/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java +++ b/src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java @@ -35,13 +35,31 @@ class EvaluationValueTest { @Test - void testUnsupportedDataType() { - final ExpressionConfiguration configuration = defaultConfiguration(); + void testUnsupportedDataTypeWithBinaryNotAllowed() { + final ExpressionConfiguration configuration = + ExpressionConfiguration.builder().binaryAllowed(false).build(); assertThatThrownBy(() -> EvaluationValue.of(Locale.FRANCE, configuration)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported data type 'java.util.Locale'"); } + @Test + void testUnsupportedDataTypeWithBinaryAllowed() { + final ExpressionConfiguration configuration = + ExpressionConfiguration.builder().binaryAllowed(true).build(); + Object rawValue = new Object(); + EvaluationValue value = EvaluationValue.of(rawValue, configuration); + assertThat(value.isStringValue()).isFalse(); + assertThat(value.isNumberValue()).isFalse(); + assertThat(value.isBooleanValue()).isFalse(); + assertThat(value.isStructureValue()).isFalse(); + assertThat(value.isArrayValue()).isFalse(); + assertThat(value.isExpressionNode()).isFalse(); + assertThat(value.isNullValue()).isFalse(); + assertThat(value.isBinaryValue()).isTrue(); + assertThat(value.getValue()).isSameAs(rawValue); + } + @Test void testString() { EvaluationValue value = EvaluationValue.of("Hello World", defaultConfiguration()); @@ -53,6 +71,7 @@ void testString() { assertThat(value.isArrayValue()).isFalse(); assertThat(value.isExpressionNode()).isFalse(); assertThat(value.isNullValue()).isFalse(); + assertThat(value.isBinaryValue()).isFalse(); assertDataIsCorrect( value, "Hello World", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, String.class); } @@ -93,6 +112,7 @@ void testBooleanTrue() { assertThat(value.isArrayValue()).isFalse(); assertThat(value.isExpressionNode()).isFalse(); assertThat(value.isNullValue()).isFalse(); + assertThat(value.isBinaryValue()).isFalse(); assertDataIsCorrect( value, "true", BigDecimal.ONE, true, Instant.EPOCH, Duration.ZERO, Boolean.class); } @@ -407,6 +427,7 @@ void testArray() { assertThat(value.isStringValue()).isFalse(); assertThat(value.isExpressionNode()).isFalse(); assertThat(value.isNullValue()).isFalse(); + assertThat(value.isBinaryValue()).isFalse(); assertThat(value.getArrayValue()).hasSize(2); assertThat(value.getArrayValue().get(0).getStringValue()).isEqualTo("1"); @@ -443,6 +464,7 @@ void testStructure() { assertThat(value.isArrayValue()).isFalse(); assertThat(value.isExpressionNode()).isFalse(); assertThat(value.isNullValue()).isFalse(); + assertThat(value.isBinaryValue()).isFalse(); assertThat(value.getStructureValue()).hasSize(2); assertThat(value.getStructureValue().get("a").getStringValue()).isEqualTo("Hello"); @@ -476,6 +498,7 @@ void testExpressionNode() { assertThat(value.isArrayValue()).isFalse(); assertThat(value.isStringValue()).isFalse(); assertThat(value.isNullValue()).isFalse(); + assertThat(value.isBinaryValue()).isFalse(); assertDataIsCorrect( value, @@ -537,6 +560,7 @@ void testNull() { assertThat(value.isArrayValue()).isFalse(); assertThat(value.isExpressionNode()).isFalse(); assertThat(value.isNullValue()).isTrue(); + assertThat(value.isBinaryValue()).isFalse(); assertDataIsCorrect(value, null, null, null); } diff --git a/src/test/java/com/ezylang/evalex/data/conversion/BinaryConverterTest.java b/src/test/java/com/ezylang/evalex/data/conversion/BinaryConverterTest.java new file mode 100644 index 00000000..635d0521 --- /dev/null +++ b/src/test/java/com/ezylang/evalex/data/conversion/BinaryConverterTest.java @@ -0,0 +1,45 @@ +/* + Copyright 2012-2024 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 org.junit.jupiter.api.Test; + +class BinaryConverterTest { + + private final ExpressionConfiguration defaultConfiguration = + ExpressionConfiguration.defaultConfiguration(); + + private final BinaryConverter converter = new BinaryConverter(); + + @Test + void testObject() { + Object object = new Object(); + + EvaluationValue converted = converter.convert(object, defaultConfiguration); + + assertThat(converted.getDataType()).isEqualTo(EvaluationValue.DataType.BINARY); + assertThat(converted.getValue()).isSameAs(object); + } + + @Test + void testCanConvert() { + assertThat(converter.canConvert(new Object())).isTrue(); + } +}