Skip to content

Commit

Permalink
feat(SNSUNI-80): support quantity parsing without square brackets
Browse files Browse the repository at this point in the history
  • Loading branch information
pjazdzyk committed Jan 28, 2024
1 parent 65b03d3 commit 1eebf3d
Show file tree
Hide file tree
Showing 25 changed files with 270 additions and 179 deletions.
70 changes: 44 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<dependency>
<groupId>com.synerset</groupId>
<artifactId>unitility-core</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
</dependency>
```
If you use frameworks to develop web applications, it is recommended to use Unitility extension modules,
Expand All @@ -78,15 +78,15 @@ Extension for the Spring Boot framework:
<dependency>
<groupId>com.synerset</groupId>
<artifactId>unitility-spring</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
</dependency>
```
Extension for the Quarkus framework:
```xml
<dependency>
<groupId>com.synerset</groupId>
<artifactId>unitility-quarkus</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
</dependency>
```
Extensions include CORE module, so you don't have to put it separate in your pom.
Expand Down Expand Up @@ -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)}
```

Expand All @@ -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. <Br>
**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.<br>
DO NOT USE grouping separators. <br>
This will parse properly:<br>
```text
1000000.00 [Pa]
```
But this will not: <br>
```text
1,000,0000.00 [Pa]
```



### 4.3 Logical operations

* basic logic operations
Expand Down Expand Up @@ -383,7 +399,7 @@ with ready serializers/deserializers and integration with the most popular frame

**IMPORTANT:** <br>
**a)** JSON request body of PhysicalQuantity should follow the structure shown in the section below (5.1) <br>
**b)** Sending request via path param or query param, **engineering format** should be used i.e.: 20.0[oC]<br>
**b)** Sending request via path param or query param, should be used i.e.: 20.0oC, without square brackets or special characters.<br>

---

Expand All @@ -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
Expand All @@ -418,7 +436,7 @@ deserialization back to Java objects. To include this module in your project, us
<dependency>
<groupId>com.synerset</groupId>
<artifactId>unitility-jackson</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
</dependency>
```
PhysicalQuantity JSON structure for valid serialization / deserialization has been defined as in the following example:
Expand All @@ -435,15 +453,15 @@ 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** <br>
Module tested for Spring Boot platform version: **3.2.2** <br>
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:
```xml
<dependency>
<groupId>com.synerset</groupId>
<artifactId>unitility-spring</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
</dependency>
```
Adding Spring module to the project will automatically:
Expand Down Expand Up @@ -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).<br>
see a section: [Registering custom quantity in Spring](#63-registering-custom-quantities-in-spring).<br>

## 5.3 Quarkus module
Module tested for Quarkus platform version: **3.5.1**<br>
Expand All @@ -483,7 +501,7 @@ add following dependency:
<dependency>
<groupId>com.synerset</groupId>
<artifactId>unitility-quarkus</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
</dependency>
```
Adding Quarkus module to the project will automatically:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
```

Expand Down
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,22 @@

<properties>
<!-- MODULES VERSION -->
<project.version>2.1.1</project.version>
<project.version>2.2.0</project.version>
<!-- Maven Properties -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Test dependencies versions -->
<jacoco.version>0.8.11</jacoco.version>
<junit-jupiter.version>5.10.1</junit-jupiter.version>
<assertj-core.version>3.24.2</assertj-core.version>
<assertj-core.version>3.25.2</assertj-core.version>
<!-- Framework versions -->
<jackson-databind.version>2.16.1</jackson-databind.version>
<spring-boot-starter-web.version>3.2.1</spring-boot-starter-web.version>
<quarkus-version>3.6.4</quarkus-version>
<spring-boot-starter-web.version>3.2.2</spring-boot-starter-web.version>
<quarkus-version>3.7.0</quarkus-version>
<jandex-maven-plugin.version>3.1.6</jandex-maven-plugin.version>
<!-- Plugin versions -->
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
<maven-javadoc-plugin.version>3.6.3</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.3.0</maven-source-plugin.version>
<nexus-staging-maven-plugin.version>1.6.13</nexus-staging-maven-plugin.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,16 +59,24 @@ default <U extends Unit, Q extends PhysicalQuantity<U>> Q parseFromSymbol(Class<
default <U extends Unit, Q extends PhysicalQuantity<U>> Q parseFromEngFormat(Class<Q> 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);
}

/**
Expand Down Expand Up @@ -100,36 +109,6 @@ private <U extends Unit, Q extends PhysicalQuantity<U>> 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());
}
}

}
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down Expand Up @@ -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.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public <U extends Unit, Q extends PhysicalQuantity<U>> Q parseFromDMSFormat(Clas

String preparedInput = StringTransformer.of(quantityInDMSFormat)
.trimLowerAndClean()
.removeParentheses()
.dropParentheses()
.replaceCommaForDot()
.unifyDMSNotationSymbols()
.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private static String unifySymbol(String inputString) {
return StringTransformer.of(inputString)
.trimLowerAndClean()
.unifyMultiAndDiv()
.removeParentheses()
.dropParentheses()
.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static String unifySymbol(String inputString) {
return StringTransformer.of(inputString)
.trimLowerAndClean()
.unifyMultiAndDiv()
.removeParentheses()
.dropParentheses()
.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static String unifySymbol(String inputString) {
return StringTransformer.of(inputString)
.trimLowerAndClean()
.unifyMultiAndDiv()
.removeParentheses()
.dropParentheses()
.unifySymbolsOfAngle()
.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static String unifySymbol(String inputString) {
return StringTransformer.of(inputString)
.trimLowerAndClean()
.unifyMultiAndDiv()
.removeParentheses()
.dropParentheses()
.dropDegreeSymbols()
.toString();
}
Expand Down
Loading

0 comments on commit 1eebf3d

Please sign in to comment.