From 1eebf3d735385e22d2bbf2edc0839e8c0f0e1e17 Mon Sep 17 00:00:00 2001 From: Piotr Jazdzyk Date: Sun, 28 Jan 2024 11:05:09 +0100 Subject: [PATCH] feat(SNSUNI-80): support quantity parsing without square brackets --- README.md | 70 ++++++++++------ pom.xml | 10 +-- .../PhysicalQuantityParsingFactory.java | 51 ++++-------- .../geographic/DMSParserHelper.java | 12 +-- .../geographic/GeoQuantityParsingFactory.java | 2 +- .../unitsystem/mechanical/MomentumUnits.java | 2 +- .../thermodynamic/DynamicViscosityUnits.java | 2 +- .../thermodynamic/SpecificHeatUnits.java | 2 +- .../ThermalConductivityUnits.java | 2 +- .../unitsystem/utils/DoubleParser.java | 53 ++++++++++++ .../unitsystem/utils/StringTransformer.java | 82 +++++++++++++++++-- .../unitsystem/utils/ValueFormatter.java | 2 +- .../customunits/CustomAngleUnitTest.java | 1 - .../PhysicalQuantityParsingFactoryTest.java | 3 +- .../jackson/serdes/LatitudeDeserializer.java | 10 +-- .../jackson/serdes/LongitudeDeserializer.java | 24 ++---- .../serdes/PhysicalQuantityDeserializer.java | 6 +- .../jackson/serdes/SerdesHelpers.java | 13 ++- ...ysicalQuantityJacksonDeserializerTest.java | 19 +++++ .../quarkus/serdes/ConverterHelpers.java | 19 ----- .../serdes/LatitudeParamJakartaConverter.java | 11 +-- .../LongitudeParamJakartaConverter.java | 11 +-- .../spring/serdes/ConverterHelpers.java | 20 ----- .../serdes/LatitudeWebMvcConverter.java | 11 +-- .../serdes/LongitudeWebMvcConverter.java | 11 +-- 25 files changed, 270 insertions(+), 179 deletions(-) create mode 100644 unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/DoubleParser.java delete mode 100644 unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/ConverterHelpers.java delete mode 100644 unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/ConverterHelpers.java diff --git a/README.md b/README.md index aeff1f2..7055216 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,13 @@ features, such as overloaded operators. Copy the Maven dependency provided below to your pom.xml file, and you are ready to go. For other package managers, check maven central repository: -[UNITILITY](https://search.maven.org/artifact/com.synerset/unitility/2.1.1/jar?eh=). +[UNITILITY](https://search.maven.org/artifact/com.synerset/unitility/2.2.0/jar?eh=). ```xml com.synerset unitility-core - 2.1.1 + 2.2.0 ``` If you use frameworks to develop web applications, it is recommended to use Unitility extension modules, @@ -78,7 +78,7 @@ Extension for the Spring Boot framework: com.synerset unitility-spring - 2.1.1 + 2.2.0 ``` Extension for the Quarkus framework: @@ -86,7 +86,7 @@ Extension for the Quarkus framework: com.synerset unitility-quarkus - 2.1.1 + 2.2.0 ``` Extensions include CORE module, so you don't have to put it separate in your pom. @@ -232,24 +232,26 @@ programming style using the io.vavr library. ### 4.2 Parsing quantities from string The physical quantity can be instantiated from a string representing the commonly used engineering style of writing -values with units: "{value}[{unit}]", for example, "20.5 [K]". To parse a valid string into a PhysicalQuantity, you need -to obtain an instance of the parsing factory provided in the core module. The default parsing factory includes parsers -for all supported physical quantities and their related units. +values with units: "{value}[{unit}]", for example, "20.5 [K]", but it will also accept input without square brackets. +To parse a valid string into a PhysicalQuantity, you need to obtain an instance of the parsing factory provided in the core module. +The default parsing factory includes parsers for all supported physical quantities and their related units. ```java // Create default parsing factory -PhysicalQuantityParsingFactory parsingFactory = - PhysicalQuantityParsingFactory.DEFAULT_PARSING_FACTORY; +PhysicalQuantityParsingFactory parsingFactory = PhysicalQuantityParsingFactory.DEFAULT_PARSING_FACTORY; // Examples of string in engineering format with unit in square brackets String k1 = "15.1 [W p mxK)]"; String k2 = "15.1 [W/(m.K)]"; String k3 = " 1 5 , 1 [ WpmK ]"; +String k4 = "15.1 W/mK"; // All above strings are properly resolved to Thermal Conductivity, even partially malformed k3. -ThermalConductivity thermCond1 = parsingFactory.fromEngFormat(ThermalConductivity.class, k1); +ThermalConductivity thermCond1 = parsingFactory.parseFromEngFormat(ThermalConductivity.class, k1); // will resolve to {15.1 W/(m·K)} -ThermalConductivity thermCond2 = parsingFactory.fromEngFormat(ThermalConductivity.class, k2); +ThermalConductivity thermCond2 = parsingFactory.parseFromEngFormat(ThermalConductivity.class, k2); // will resolve to {15.1 W/(m·K)} -ThermalConductivity thermCond3 = parsingFactory.fromEngFormat(ThermalConductivity.class, k3); +ThermalConductivity thermCond3 = parsingFactory.parseFromEngFormat(ThermalConductivity.class, k3); +// will resolve to {15.1 W/(m·K)} +ThermalConductivity thermCond4 = parsingRegistry.parseFromEngFormat(ThermalConductivity.class, k4); // will resolve to {15.1 W/(m·K)} ``` @@ -267,6 +269,20 @@ alternative ways of expressing units in an input string: Please note that this method of creating quantities is designed to be used for deserializers.
**In your code, you should create units in a programmatic way, not parsing from strings.** +IMPORTANT: +When parsing from string values should be provided using dot "." as decimal separator.
+DO NOT USE grouping separators.
+This will parse properly:
+```text +1000000.00 [Pa] +``` +But this will not:
+```text +1,000,0000.00 [Pa] +``` + + + ### 4.3 Logical operations * basic logic operations @@ -383,7 +399,7 @@ with ready serializers/deserializers and integration with the most popular frame **IMPORTANT:**
**a)** JSON request body of PhysicalQuantity should follow the structure shown in the section below (5.1)
-**b)** Sending request via path param or query param, **engineering format** should be used i.e.: 20.0[oC]
+**b)** Sending request via path param or query param, should be used i.e.: 20.0oC, without square brackets or special characters.
--- @@ -406,9 +422,11 @@ Json request body example: ``` Using as path param: ```text -http://localhost:8080/api/v1/temperatures/20.5[oC] +/api/v1/temperatures/20.5C ``` - +Make sure that quantities used in path variables or query parameters are without square brackets. Parsers are designed to +filter them out, but some recent versions of Tomcat server have issues with "[]" so it is better to avoid using them. +Other special characters can be easily replaced with simpler equivalents, as presented in the table in [section 4.2](#42-parsing-quantities-from-string). ## 5.1 Jackson serializers and deserializers The Jackson library is utilized in many frameworks, enabling the serialization of objects into a JSON structure and @@ -418,7 +436,7 @@ deserialization back to Java objects. To include this module in your project, us com.synerset unitility-jackson - 2.1.1 + 2.2.0 ``` PhysicalQuantity JSON structure for valid serialization / deserialization has been defined as in the following example: @@ -435,7 +453,7 @@ obtain the appropriate parser depending on the class type. This module is part o therefore, it does not need to be added explicitly if framework extensions are included in the project. ## 5.2 Spring Boot module -Module tested for Spring Boot platform version: **3.1.5**
+Module tested for Spring Boot platform version: **3.2.2**
Spring Boot module includes **unitility-jackson** and **unitility-core** modules, and it will automatically create required beans through the autoconfiguration dependency injection mechanism. To use Spring extension module, add the following dependency: @@ -443,7 +461,7 @@ add the following dependency: com.synerset unitility-spring - 2.1.1 + 2.2.0 ``` Adding Spring module to the project will automatically: @@ -472,7 +490,7 @@ public class DefaultUnitsController { ``` For special cases when custom unit is created, which is not a part of standard Unitiltiy package, additional configuration steps must be carried out to ensure that custom unit is properly resolved from JSON or path/query params. For more details -see a section: [Registering custom quantity in Spring](#62-registering-custom-units-in-spring).
+see a section: [Registering custom quantity in Spring](#63-registering-custom-quantities-in-spring).
## 5.3 Quarkus module Module tested for Quarkus platform version: **3.5.1**
@@ -483,7 +501,7 @@ add following dependency: com.synerset unitility-quarkus - 2.1.1 + 2.2.0 ``` Adding Quarkus module to the project will automatically: @@ -516,16 +534,16 @@ public class DefaultUnitsResource { ``` For special cases when custom unit is created, which is not a part of standard Unitiltiy package, additional configuration steps must be carried out to ensure that custom unit is properly resolved from JSON or path/query params. For more details -see a section: [Registering custom quantity in Quarkus](#63-registering-custom-units-in-quarkus). +see a section: [Registering custom quantity in Quarkus](#64-registering-custom-quantities-in-quarkus). ## 6. CREATING CUSTOM QUANTITIES The Unitility includes a set of the most commonly used quantities and related units with an emphasis on thermodynamics. However, the framework foundation can be successfully used to define almost any unit from economy, biology, electronics, and even for logistics to represent the quantity of bottles in different sized packages. Sooner or later, a developer might face a case where he would like to add a new unit or quantity to the library. I will be including requested units on -a regular basis. If this is not urgent, please go to the [ISSUES](https://github.com/pjazdzyk/unitility/issues) page and let me know what is needed. If you can't -wait, below are instructions on how to create a custom unit and also how to ensure that all your custom units/quantities -are registered correctly in Spring or Quarkus. +a regular basis. If this is not urgent, please go to the [ISSUES](https://github.com/pjazdzyk/unitility/issues) page and +let me know what is needed. If you can't wait, below are instructions on how to create a custom unit and also how to ensure +that all your custom units/quantities are registered correctly in Spring or Quarkus. ### 6.1 Custom unit If you need to extend standard unit definitions for a given quantity, the simplest way is to create a new unit @@ -582,7 +600,7 @@ user quantities: [CustomParsingFactory](https://github.com/pjazdzyk/unitility-spring-example/blob/master/src/main/java/com/synerset/unitility/spring/examples/newquantity/CustomParsingFactoryWithAngle.java). After a new parsing factory is created and all standard and new custom quantities parsers are properly registered, -you can now create a configuration and register new JacksonModule and new Conveter in FormatterRegistry: +you can now create a configuration and register new JacksonModule and new Converter in FormatterRegistry: [CustomAngleConfiguration](https://github.com/pjazdzyk/unitility-spring-example/blob/master/src/main/java/com/synerset/unitility/spring/examples/newquantity/CustomAngleConfiguration.java). After this step is done, you can freely use your CustomAngle unit in your web application: @@ -830,7 +848,7 @@ Latitude, Longitude can be used as JSON request body or as value in path variabl Latitude path param usage example: /routes/latitude/20°7'22.8"S/longitude/-14°7'12.4"W /routes/latitude/20o7min22.8secS/longitude/-14o7min12.4secW -/routes/latitude/20.123[deg]/longitude/-14.123[deg] +/routes/latitude/20.123deg/longitude/-14.123deg /routes/latitude/20.123/longitude/-14.123 ``` diff --git a/pom.xml b/pom.xml index d6eee12..b156f97 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ - 2.1.1 + 2.2.0 17 17 @@ -52,14 +52,14 @@ 0.8.11 5.10.1 - 3.24.2 + 3.25.2 2.16.1 - 3.2.1 - 3.6.4 + 3.2.2 + 3.7.0 3.1.6 - 1.5.0 + 1.6.0 3.6.3 3.3.0 1.6.13 diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantityParsingFactory.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantityParsingFactory.java index 520cd06..bb1d9dd 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantityParsingFactory.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantityParsingFactory.java @@ -3,6 +3,7 @@ import com.synerset.unitility.unitsystem.exceptions.UnitSystemClassNotSupportedException; import com.synerset.unitility.unitsystem.exceptions.UnitSystemParseException; import com.synerset.unitility.unitsystem.geographic.GeoQuantityParsingFactory; +import com.synerset.unitility.unitsystem.utils.DoubleParser; import com.synerset.unitility.unitsystem.utils.StringTransformer; import java.util.HashSet; @@ -58,16 +59,24 @@ default > Q parseFromSymbol(Class< default > Q parseFromEngFormat(Class targetClass, String quantityInEngFormat) { - String preparedSource = StringTransformer.of(quantityInEngFormat) + String preparedQuantityAsString = StringTransformer.of(quantityInEngFormat) .trimLowerAndClean() + .replaceCommaForDot() + .dropParentheses() .toString(); - String unitSymbol = null; - if (preparedSource.contains("[")) { - unitSymbol = extractSymbolFromEngFormat(targetClass, preparedSource); + int indexOfLastDigit = 0; + for (char letter : preparedQuantityAsString.toCharArray()) { + if (Character.isDigit(letter) || letter == '.' || letter == '-' || letter == 'e') { + indexOfLastDigit++; + } else break; } - double value = extractValueFromEngFormat(targetClass, preparedSource); - return parseFromSymbol(targetClass, value, unitSymbol); + + String valuePart = preparedQuantityAsString.substring(0, indexOfLastDigit); + String symbolPart = preparedQuantityAsString.substring(indexOfLastDigit); + double value = DoubleParser.parseToDouble(valuePart); + + return parseFromSymbol(targetClass, value, symbolPart); } /** @@ -100,36 +109,6 @@ private > void validateIfClassIsRe } } - private double extractValueFromEngFormat(Class targetClass, String inputString) { - String perparedString = StringTransformer.of(inputString) - .replaceCommaForDot() - .toString(); - - String extractedNumber; - if (perparedString.contains("[")) { - int endIndex = perparedString.indexOf('['); - extractedNumber = perparedString.substring(0, endIndex); - } else { - extractedNumber = perparedString; - } - - try { - return Double.parseDouble(extractedNumber); - } catch (Exception e) { - throw new UnitSystemParseException("Could not extract number from input: " + perparedString - + ", target class: " + targetClass.getSimpleName()); - } - } - private String extractSymbolFromEngFormat(Class targetClass, String input) { - int startIndex = input.indexOf('['); - int endIndex = input.indexOf(']', startIndex); - if (startIndex != -1 && endIndex != -1) { - return input.substring(startIndex + 1, endIndex); - } else { - throw new UnitSystemParseException("Invalid input string. Could not extract unit symbol from: " + input - + ", target class: " + targetClass.getSimpleName()); - } - } } \ No newline at end of file diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/DMSParserHelper.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/DMSParserHelper.java index 7dc3941..0223c0f 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/DMSParserHelper.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/DMSParserHelper.java @@ -1,7 +1,8 @@ package com.synerset.unitility.unitsystem.geographic; import com.synerset.unitility.unitsystem.exceptions.UnitSystemArgumentException; -import com.synerset.unitility.unitsystem.exceptions.UnitSystemParseException; + +import static com.synerset.unitility.unitsystem.utils.DoubleParser.parseToDouble; class DMSParserHelper { @@ -48,15 +49,6 @@ public static double determineSign(char directionChar, double degrees) { return sign; } - private static double parseToDouble(String doubleAsString) { - try { - return Double.parseDouble(doubleAsString.trim().replace(",", ".")); - } catch (NumberFormatException ex) { - throw new UnitSystemParseException("Geo double parser: Invalid input, could not parse to double, input = " - + doubleAsString); - } - } - private static void validateInputString(String inputString) { if (inputString == null || inputString.isBlank()) { throw new UnitSystemArgumentException("Geo parser: Invalid input. Argument cannot be null or blank."); diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/GeoQuantityParsingFactory.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/GeoQuantityParsingFactory.java index f309433..594359d 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/GeoQuantityParsingFactory.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/geographic/GeoQuantityParsingFactory.java @@ -44,7 +44,7 @@ public > Q parseFromDMSFormat(Clas String preparedInput = StringTransformer.of(quantityInDMSFormat) .trimLowerAndClean() - .removeParentheses() + .dropParentheses() .replaceCommaForDot() .unifyDMSNotationSymbols() .toString(); diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/mechanical/MomentumUnits.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/mechanical/MomentumUnits.java index 56a9238..2c0b0b4 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/mechanical/MomentumUnits.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/mechanical/MomentumUnits.java @@ -56,7 +56,7 @@ private static String unifySymbol(String inputString) { return StringTransformer.of(inputString) .trimLowerAndClean() .unifyMultiAndDiv() - .removeParentheses() + .dropParentheses() .toString(); } diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/DynamicViscosityUnits.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/DynamicViscosityUnits.java index 99fa922..b768870 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/DynamicViscosityUnits.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/DynamicViscosityUnits.java @@ -57,7 +57,7 @@ private static String unifySymbol(String inputString) { return StringTransformer.of(inputString) .trimLowerAndClean() .unifyMultiAndDiv() - .removeParentheses() + .dropParentheses() .toString(); } diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/SpecificHeatUnits.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/SpecificHeatUnits.java index a683274..c174fce 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/SpecificHeatUnits.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/SpecificHeatUnits.java @@ -57,7 +57,7 @@ private static String unifySymbol(String inputString) { return StringTransformer.of(inputString) .trimLowerAndClean() .unifyMultiAndDiv() - .removeParentheses() + .dropParentheses() .unifySymbolsOfAngle() .toString(); } diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/ThermalConductivityUnits.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/ThermalConductivityUnits.java index 2d8bcf6..9c845fb 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/ThermalConductivityUnits.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/thermodynamic/ThermalConductivityUnits.java @@ -57,7 +57,7 @@ private static String unifySymbol(String inputString) { return StringTransformer.of(inputString) .trimLowerAndClean() .unifyMultiAndDiv() - .removeParentheses() + .dropParentheses() .dropDegreeSymbols() .toString(); } diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/DoubleParser.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/DoubleParser.java new file mode 100644 index 0000000..ed7eea1 --- /dev/null +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/DoubleParser.java @@ -0,0 +1,53 @@ +package com.synerset.unitility.unitsystem.utils; + +import com.synerset.unitility.unitsystem.exceptions.UnitSystemParseException; + +/** + * The {@link DoubleParser} class provides utility methods for parsing strings to double values. + * It includes methods for parsing a string to a double with or without an additional error message. + * The class is designed with static methods and a private constructor to prevent instantiation, + * as it serves as a utility class. + */ +public class DoubleParser { + + /** + * Private constructor to prevent instantiation of the utility class. + * Throws an {@code IllegalStateException} with the message "Utility class" if called. + */ + private DoubleParser() { + throw new IllegalStateException("Utility class"); + } + + /** + * Parses the specified string to a double value. + * + * @param doubleAsString The string representation of the double. + * @param addedMessage An additional message to be included in the exception message if parsing fails. + * @return The parsed double value. + * @throws UnitSystemParseException If the input string cannot be parsed to a double, + * an exception is thrown with a descriptive error message. + */ + public static double parseToDouble(String doubleAsString, String addedMessage) { + try { + return Double.parseDouble(doubleAsString); + } catch (NumberFormatException ex) { + throw new UnitSystemParseException(addedMessage + " Invalid input, could not parse to double, input = " + + doubleAsString); + } + } + + /** + * Parses the specified string to a double value. + * Calls the {@code parseToDouble} method with an empty additional message. + * + * @param doubleAsString The string representation of the double. + * @return The parsed double value. + * @throws UnitSystemParseException If the input string cannot be parsed to a double, + * an exception is thrown with a descriptive error message. + * The additional message is empty in this case. + */ + public static double parseToDouble(String doubleAsString) { + return parseToDouble(doubleAsString, ""); + } + +} \ No newline at end of file diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/StringTransformer.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/StringTransformer.java index ed7f294..4a988da 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/StringTransformer.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/StringTransformer.java @@ -1,17 +1,38 @@ package com.synerset.unitility.unitsystem.utils; +/** + * The StringTransformer class provides utility methods for transforming strings, particularly + * those related to unit symbols and notations. It allows various operations like trimming, lowercasing, + * replacing symbols, and cleaning up string representations of units. + */ public class StringTransformer { private final String inputString; + /** + * Private constructor to create a StringTransformer instance with the specified input string. + * + * @param inputString The input string to be transformed. + */ private StringTransformer(String inputString) { this.inputString = inputString; } + /** + * Static factory method to create a StringTransformer instance with the specified input string. + * + * @param inputString The input string to be transformed. + * @return A new instance of StringTransformer. + */ public static StringTransformer of(String inputString) { return new StringTransformer(inputString); } + /** + * Trims the input string, converts to lowercase, and removes spaces. + * + * @return A new StringTransformer instance with the transformed string. + */ public StringTransformer trimLowerAndClean() { return StringTransformer.of( inputString.trim() @@ -20,21 +41,35 @@ public StringTransformer trimLowerAndClean() { ); } + /** + * Replaces superscript symbols for cubic and square units with numeric equivalents. + * + * @return A new StringTransformer instance with the transformed string. + */ public StringTransformer unifyAerialAndVol() { return StringTransformer.of( inputString.replace("³", "3") .replace("²", "2")); } + /** + * Replaces various multiplication and division symbols with a common representation. + * + * @return A new StringTransformer instance with the transformed string. + */ public StringTransformer unifyMultiAndDiv() { return StringTransformer.of( inputString.replace(".", "") .replace("·", "") .replace("x", "") .replace("/", "p")); - } + /** + * Replaces symbols representing angles with standardized representations. + * + * @return A new StringTransformer instance with the transformed string. + */ public StringTransformer unifySymbolsOfAngle() { return StringTransformer.of( inputString.replace("°", "o") @@ -43,6 +78,11 @@ public StringTransformer unifySymbolsOfAngle() { ); } + /** + * Removes degree symbols and notations from the input string. + * + * @return A new StringTransformer instance with the transformed string. + */ public StringTransformer dropDegreeSymbols() { return StringTransformer.of( inputString.replace("°", "") @@ -51,23 +91,44 @@ public StringTransformer dropDegreeSymbols() { ); } - public StringTransformer removeParentheses() { + /** + * Removes parentheses from the input string. + * + * @return A new StringTransformer instance with the transformed string. + */ + public StringTransformer dropParentheses() { + return StringTransformer.of( + inputString.replaceAll("[()\\[\\]{}<>]", "") + ); + } + + /** + * Removes square brackets from the input string. + * + * @return A new StringTransformer instance with the transformed string. + */ + public StringTransformer dropSquareBrackets() { return StringTransformer.of( - inputString.replace("(", "") - .replace(")", "") - .replace("}", "") - .replace("{", "") - .replace("<", "") - .replace(">", "") + inputString.replace("[", "") ); } + /** + * Replaces commas with dots in the input string. + * + * @return A new StringTransformer instance with the transformed string. + */ public StringTransformer replaceCommaForDot() { return StringTransformer.of( inputString.replace(",", ".") ); } + /** + * Unifies symbols for Degrees, Minutes, and Seconds notation. + * + * @return A new StringTransformer instance with the transformed string. + */ public StringTransformer unifyDMSNotationSymbols() { String transformedString = inputString.replace("°", "o") .replace("deg", "o") @@ -83,6 +144,11 @@ public StringTransformer unifyDMSNotationSymbols() { return StringTransformer.of(transformedString); } + /** + * Returns the original input string without any transformations. + * + * @return The original input string. + */ @Override public String toString() { return inputString; diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/ValueFormatter.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/ValueFormatter.java index db42845..ce87d6d 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/ValueFormatter.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/utils/ValueFormatter.java @@ -51,4 +51,4 @@ private static String formatValueToDecimalPlaces(double value, int numDecimalPla return decimalFormat.format(value); } -} +} \ No newline at end of file diff --git a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunits/CustomAngleUnitTest.java b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunits/CustomAngleUnitTest.java index 37e6305..8584203 100644 --- a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunits/CustomAngleUnitTest.java +++ b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunits/CustomAngleUnitTest.java @@ -1,7 +1,6 @@ package com.synerset.unitility.unitsystem.customunits; import com.synerset.unitility.unitsystem.common.Angle; -import com.synerset.unitility.unitsystem.customunits.CustomAngleUnits; import org.junit.jupiter.api.Test; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; diff --git a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/parsers/PhysicalQuantityParsingFactoryTest.java b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/parsers/PhysicalQuantityParsingFactoryTest.java index a1081d6..9b2250f 100644 --- a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/parsers/PhysicalQuantityParsingFactoryTest.java +++ b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/parsers/PhysicalQuantityParsingFactoryTest.java @@ -6,7 +6,6 @@ import com.synerset.unitility.unitsystem.Unit; import com.synerset.unitility.unitsystem.common.DistanceUnit; import com.synerset.unitility.unitsystem.exceptions.UnitSystemClassNotSupportedException; -import com.synerset.unitility.unitsystem.exceptions.UnitSystemParseException; import com.synerset.unitility.unitsystem.thermodynamic.Temperature; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -57,7 +56,7 @@ void createFromEngFormat_shouldFailIfQueriedForNonSupportedClass() { // When // Then - assertThrows(UnitSystemParseException.class, + assertThrows(UnitSystemClassNotSupportedException.class, () -> parsingRegistry.parseFromEngFormat(TestClass.class, "20C")); } diff --git a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LatitudeDeserializer.java b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LatitudeDeserializer.java index 981293e..b4d88db 100644 --- a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LatitudeDeserializer.java +++ b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LatitudeDeserializer.java @@ -11,6 +11,7 @@ import java.io.IOException; +import static com.synerset.unitility.jackson.serdes.SerdesHelpers.containsNonDigitChars; import static com.synerset.unitility.jackson.serdes.SerdesHelpers.prepareInput; public class LatitudeDeserializer extends JsonDeserializer { @@ -33,17 +34,16 @@ public Latitude deserialize(JsonParser jsonParser, DeserializationContext deseri } String quantityValue = valueFieldNode.asText(); - - if (quantityValue != null && quantityValue.contains("[")) { - return deserializeFromEngineeringFormat(quantityValue); - } - String preparedQuantityValue = prepareInput(quantityValue); if (preparedQuantityValue != null && DMSValidator.isValidDMSFormat(preparedQuantityValue)) { return deserializeFromDMSFormat(preparedQuantityValue); } + if (quantityValue != null && containsNonDigitChars(quantityValue)) { + return deserializeFromEngineeringFormat(quantityValue); + } + return deserializeFromSymbolAndValue(quantityNode); } diff --git a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LongitudeDeserializer.java b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LongitudeDeserializer.java index 40ce7d6..82770b0 100644 --- a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LongitudeDeserializer.java +++ b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/LongitudeDeserializer.java @@ -8,10 +8,12 @@ import com.synerset.unitility.unitsystem.geographic.DMSValidator; import com.synerset.unitility.unitsystem.geographic.GeoQuantityParsingFactory; import com.synerset.unitility.unitsystem.geographic.Longitude; -import com.synerset.unitility.unitsystem.utils.StringTransformer; import java.io.IOException; +import static com.synerset.unitility.jackson.serdes.SerdesHelpers.containsNonDigitChars; +import static com.synerset.unitility.jackson.serdes.SerdesHelpers.prepareInput; + public class LongitudeDeserializer extends JsonDeserializer { protected final GeoQuantityParsingFactory geoParsingFactory; @@ -32,17 +34,16 @@ public Longitude deserialize(JsonParser jsonParser, DeserializationContext deser } String quantityValue = valueFieldNode.asText(); - - if (quantityValue != null && quantityValue.contains("[")) { - return deserializeFromEngineeringFormat(quantityValue); - } - String preparedQuantityValue = prepareInput(quantityValue); if (preparedQuantityValue != null && DMSValidator.isValidDMSFormat(preparedQuantityValue)) { return deserializeFromDMSFormat(preparedQuantityValue); } + if (quantityValue != null && containsNonDigitChars(quantityValue)) { + return deserializeFromEngineeringFormat(quantityValue); + } + return deserializeFromSymbolAndValue(quantityNode); } @@ -66,15 +67,4 @@ private Longitude deserializeFromSymbolAndValue(JsonNode node) { return geoParsingFactory.parseFromSymbol(Longitude.class, value, unitSymbol); } - private String prepareInput(String quantityInput) { - if (quantityInput == null) { - return null; - } - return StringTransformer.of(quantityInput) - .trimLowerAndClean() - .replaceCommaForDot() - .unifyDMSNotationSymbols() - .toString(); - } - } \ No newline at end of file diff --git a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/PhysicalQuantityDeserializer.java b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/PhysicalQuantityDeserializer.java index 1209219..bcad258 100644 --- a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/PhysicalQuantityDeserializer.java +++ b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/PhysicalQuantityDeserializer.java @@ -10,6 +10,8 @@ import java.io.IOException; +import static com.synerset.unitility.jackson.serdes.SerdesHelpers.containsNonDigitChars; + /** * The {@link PhysicalQuantityDeserializer} class is a Jackson JSON deserializer for deserializing JSON representations * of {@link PhysicalQuantity} instances. @@ -48,8 +50,8 @@ public Q deserialize(JsonParser jsonParser, DeserializationContext deserializati } String value = valueFieldNode.asText(); - if (value.contains("[")) { - // In this case, symbol field will be ignored, unit in [] will take precedence + if (containsNonDigitChars(value)) { + // In this case, symbol field will be ignored. Unit symbol is assumed to be part of a value string. return deserializeFromEngineeringFormat(value); } return deserializeFromSymbolAndValue(node); diff --git a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/SerdesHelpers.java b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/SerdesHelpers.java index fc1a95c..423145b 100644 --- a/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/SerdesHelpers.java +++ b/unitility-jackson/src/main/java/com/synerset/unitility/jackson/serdes/SerdesHelpers.java @@ -2,13 +2,13 @@ import com.synerset.unitility.unitsystem.utils.StringTransformer; -class SerdesHelpers { +public class SerdesHelpers { private SerdesHelpers() { throw new IllegalStateException("Utility class"); } - static String prepareInput(String quantityInput) { + public static String prepareInput(String quantityInput) { if (quantityInput == null) { return null; } @@ -19,4 +19,13 @@ static String prepareInput(String quantityInput) { .toString(); } + public static boolean containsNonDigitChars(String inputString) { + for (char nextChar : inputString.toCharArray()) { + if (Character.isAlphabetic(nextChar)) { + return true; + } + } + return false; + } + } \ No newline at end of file diff --git a/unitility-jackson/src/test/java/com/synerset/unitility/jackson/PhysicalQuantityJacksonDeserializerTest.java b/unitility-jackson/src/test/java/com/synerset/unitility/jackson/PhysicalQuantityJacksonDeserializerTest.java index 3cdb0f9..ad669a5 100644 --- a/unitility-jackson/src/test/java/com/synerset/unitility/jackson/PhysicalQuantityJacksonDeserializerTest.java +++ b/unitility-jackson/src/test/java/com/synerset/unitility/jackson/PhysicalQuantityJacksonDeserializerTest.java @@ -76,6 +76,25 @@ void deserialize_shouldDeserializeJsonToPhysicalQuantityFromEngFormat() throws J assertThat(actualTemp2).isEqualTo(expetedTemperature); } + @Test + void deserialize_shouldDeserializeJsonToPhysicalQuantityFromEngFormatWithoutBrackets() throws JsonProcessingException { + // Given + PhysicalQuantityParsingFactory parsingFactory = PhysicalQuantityParsingFactory.DEFAULT_PARSING_FACTORY; + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new PhysicalQuantityJacksonModule(parsingFactory)); + + String tempInput1 = "{\"value\":\"20.123 °C\"}"; + String tempInput2 = "{\"value\":\"20.123 °C\", \"unit\":\"K\"}"; + + // When + Temperature actualTemp1 = objectMapper.readValue(tempInput1, Temperature.class); + Temperature actualTemp2 = objectMapper.readValue(tempInput2, Temperature.class); + + Temperature expetedTemperature = Temperature.ofCelsius(20.123); + assertThat(actualTemp1).isEqualTo(expetedTemperature); + assertThat(actualTemp2).isEqualTo(expetedTemperature); + } + @Test void deserialize_shouldDeserializeJsonToLatitudeAndLongitude() throws JsonProcessingException { // Given diff --git a/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/ConverterHelpers.java b/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/ConverterHelpers.java deleted file mode 100644 index 48e01ca..0000000 --- a/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/ConverterHelpers.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.synerset.unitility.quarkus.serdes; - -import com.synerset.unitility.unitsystem.utils.StringTransformer; - -class ConverterHelpers { - - private ConverterHelpers() { - throw new IllegalStateException("Utility class"); - } - - static String prepareInput(String quantityInput) { - return StringTransformer.of(quantityInput) - .trimLowerAndClean() - .replaceCommaForDot() - .unifyDMSNotationSymbols() - .toString(); - } - -} diff --git a/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LatitudeParamJakartaConverter.java b/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LatitudeParamJakartaConverter.java index c5186a1..713260e 100644 --- a/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LatitudeParamJakartaConverter.java +++ b/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LatitudeParamJakartaConverter.java @@ -1,5 +1,6 @@ package com.synerset.unitility.quarkus.serdes; +import com.synerset.unitility.jackson.serdes.SerdesHelpers; import com.synerset.unitility.unitsystem.PhysicalQuantity; import com.synerset.unitility.unitsystem.common.AngleUnits; import com.synerset.unitility.unitsystem.geographic.DMSValidator; @@ -34,16 +35,16 @@ public Latitude fromString(String latitudeAsString) { throw new IllegalArgumentException("Jakarta param deserialization failure. Input cannot be null"); } - if (latitudeAsString.contains("[")) { - return geoParsingRegistry.parseFromEngFormat(Latitude.class, latitudeAsString); - } - - String preparedInput = ConverterHelpers.prepareInput(latitudeAsString); + String preparedInput = SerdesHelpers.prepareInput(latitudeAsString); if (preparedInput != null && DMSValidator.isValidDMSFormat(preparedInput)) { return geoParsingRegistry.parseFromDMSFormat(Latitude.class, preparedInput); } + if (SerdesHelpers.containsNonDigitChars(latitudeAsString)) { + return geoParsingRegistry.parseFromEngFormat(Latitude.class, latitudeAsString); + } + double potentialDoubleValue = Double.parseDouble(preparedInput); return geoParsingRegistry.parseFromSymbol(Latitude.class, potentialDoubleValue, AngleUnits.DEGREES.getSymbol()); diff --git a/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LongitudeParamJakartaConverter.java b/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LongitudeParamJakartaConverter.java index 8c49789..a4af870 100644 --- a/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LongitudeParamJakartaConverter.java +++ b/unitility-quarkus/src/main/java/com/synerset/unitility/quarkus/serdes/LongitudeParamJakartaConverter.java @@ -1,5 +1,6 @@ package com.synerset.unitility.quarkus.serdes; +import com.synerset.unitility.jackson.serdes.SerdesHelpers; import com.synerset.unitility.unitsystem.common.AngleUnits; import com.synerset.unitility.unitsystem.geographic.DMSValidator; import com.synerset.unitility.unitsystem.geographic.GeoQuantityParsingFactory; @@ -33,16 +34,16 @@ public Longitude fromString(String longitudeAsString) { throw new IllegalArgumentException("Jakarta param deserialization failure. Input cannot be null"); } - if (longitudeAsString.contains("[")) { - return geoParsingRegistry.parseFromEngFormat(Longitude.class, longitudeAsString); - } - - String preparedInput = ConverterHelpers.prepareInput(longitudeAsString); + String preparedInput = SerdesHelpers.prepareInput(longitudeAsString); if (preparedInput != null && DMSValidator.isValidDMSFormat(preparedInput)) { return geoParsingRegistry.parseFromDMSFormat(Longitude.class, preparedInput); } + if (SerdesHelpers.containsNonDigitChars(longitudeAsString)) { + return geoParsingRegistry.parseFromEngFormat(Longitude.class, longitudeAsString); + } + double potentialDoubleValue = Double.parseDouble(preparedInput); return geoParsingRegistry.parseFromSymbol(Longitude.class, potentialDoubleValue, AngleUnits.DEGREES.getSymbol()); diff --git a/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/ConverterHelpers.java b/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/ConverterHelpers.java deleted file mode 100644 index 85bed7a..0000000 --- a/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/ConverterHelpers.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.synerset.unitility.spring.serdes; - -import com.synerset.unitility.unitsystem.utils.StringTransformer; -import org.springframework.lang.NonNull; - -class ConverterHelpers { - - private ConverterHelpers() { - throw new IllegalStateException("Utility class"); - } - - static String prepareInput(@NonNull String quantityInput) { - return StringTransformer.of(quantityInput) - .trimLowerAndClean() - .replaceCommaForDot() - .unifyDMSNotationSymbols() - .toString(); - } - -} diff --git a/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LatitudeWebMvcConverter.java b/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LatitudeWebMvcConverter.java index 0da3a68..67925df 100644 --- a/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LatitudeWebMvcConverter.java +++ b/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LatitudeWebMvcConverter.java @@ -1,5 +1,6 @@ package com.synerset.unitility.spring.serdes; +import com.synerset.unitility.jackson.serdes.SerdesHelpers; import com.synerset.unitility.unitsystem.common.AngleUnits; import com.synerset.unitility.unitsystem.geographic.DMSValidator; import com.synerset.unitility.unitsystem.geographic.GeoQuantityParsingFactory; @@ -29,16 +30,16 @@ public LatitudeWebMvcConverter(GeoQuantityParsingFactory parsingFactory) { @Override public Latitude convert(@NonNull String latitudeAsString) { - if (latitudeAsString.contains("[")) { - return geoParsingRegistry.parseFromEngFormat(Latitude.class, latitudeAsString); - } - - String preparedInput = ConverterHelpers.prepareInput(latitudeAsString); + String preparedInput = SerdesHelpers.prepareInput(latitudeAsString); if (preparedInput != null && DMSValidator.isValidDMSFormat(preparedInput)) { return geoParsingRegistry.parseFromDMSFormat(Latitude.class, preparedInput); } + if (SerdesHelpers.containsNonDigitChars(latitudeAsString)) { + return geoParsingRegistry.parseFromEngFormat(Latitude.class, latitudeAsString); + } + double potentialDoubleValue = Double.parseDouble(preparedInput); return geoParsingRegistry.parseFromSymbol(Latitude.class, potentialDoubleValue, AngleUnits.DEGREES.getSymbol()); diff --git a/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LongitudeWebMvcConverter.java b/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LongitudeWebMvcConverter.java index 31ba41e..42afba0 100644 --- a/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LongitudeWebMvcConverter.java +++ b/unitility-spring/src/main/java/com/synerset/unitility/spring/serdes/LongitudeWebMvcConverter.java @@ -1,5 +1,6 @@ package com.synerset.unitility.spring.serdes; +import com.synerset.unitility.jackson.serdes.SerdesHelpers; import com.synerset.unitility.unitsystem.common.AngleUnits; import com.synerset.unitility.unitsystem.geographic.DMSValidator; import com.synerset.unitility.unitsystem.geographic.GeoQuantityParsingFactory; @@ -29,16 +30,16 @@ public LongitudeWebMvcConverter(GeoQuantityParsingFactory parsingFactory) { @Override public Longitude convert(@NonNull String longitudeAsString) { - if (longitudeAsString.contains("[")) { - return geoParsingRegistry.parseFromEngFormat(Longitude.class, longitudeAsString); - } - - String preparedInput = ConverterHelpers.prepareInput(longitudeAsString); + String preparedInput = SerdesHelpers.prepareInput(longitudeAsString); if (preparedInput != null && DMSValidator.isValidDMSFormat(preparedInput)) { return geoParsingRegistry.parseFromDMSFormat(Longitude.class, preparedInput); } + if (SerdesHelpers.containsNonDigitChars(longitudeAsString)) { + return geoParsingRegistry.parseFromEngFormat(Longitude.class, longitudeAsString); + } + double potentialDoubleValue = Double.parseDouble(preparedInput); return geoParsingRegistry.parseFromSymbol(Longitude.class, potentialDoubleValue, AngleUnits.DEGREES.getSymbol());