summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrien Hopkins <ahopk127@my.yorku.ca>2021-12-02 16:55:05 -0500
committerAdrien Hopkins <ahopk127@my.yorku.ca>2021-12-02 16:55:05 -0500
commitcc79db65bc347c50267d0a719278ef1d90cf6b1a (patch)
tree65717e8d7e75247ced380a72842c50b94fc61a0e
parentc4ae6887c32ee8d2e9e8853ffd12721d4db2fd3b (diff)
parent9c30c3ad4d4658964e2bb2bb5be6c2eebbfbe8af (diff)
Merge branch 'release-0.3.2'
-rw-r--r--.classpath1
-rw-r--r--CHANGELOG.org6
-rw-r--r--README.org2
-rw-r--r--src/main/java/sevenUnits/ProgramInfo.java2
-rw-r--r--src/main/java/sevenUnits/unit/BritishImperial.java39
-rw-r--r--src/main/java/sevenUnits/unit/LinearUnitValue.java17
-rw-r--r--src/main/java/sevenUnits/unit/UnitDatabase.java55
-rw-r--r--src/main/java/sevenUnits/unit/UnitValue.java3
-rw-r--r--src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java2
-rw-r--r--src/main/java/sevenUnits/utils/ExpressionParser.java12
-rw-r--r--src/main/java/sevenUnits/utils/ObjectProduct.java135
-rw-r--r--src/main/java/sevenUnits/utils/UncertainDouble.java15
-rw-r--r--src/test/java/sevenUnits/unit/MultiUnitTest.java4
-rw-r--r--src/test/java/sevenUnits/unit/UnitDatabaseTest.java385
-rw-r--r--src/test/java/sevenUnits/unit/UnitTest.java92
-rw-r--r--src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java1
-rw-r--r--src/test/java/sevenUnits/utils/ExpressionParserTest.java70
-rw-r--r--src/test/java/sevenUnits/utils/ObjectProductTest.java1
-rw-r--r--src/test/java/sevenUnits/utils/UncertainDoubleTest.java90
-rw-r--r--src/test/resources/test-dimensionfile-invalid1.txt3
-rw-r--r--src/test/resources/test-dimensionfile-invalid2.txt1
-rw-r--r--src/test/resources/test-dimensionfile-invalid3.txt5
-rw-r--r--src/test/resources/test-dimensionfile-valid1.txt12
-rw-r--r--src/test/resources/test-unitsfile-invalid1.txt1
-rw-r--r--src/test/resources/test-unitsfile-invalid2.txt1
-rw-r--r--src/test/resources/test-unitsfile-invalid3.txt1
-rw-r--r--src/test/resources/test-unitsfile-invalid4.txt1
-rw-r--r--src/test/resources/test-unitsfile-invalid5.txt1
-rw-r--r--src/test/resources/test-unitsfile-valid1.txt6
-rw-r--r--src/test/resources/test-unitsfile-valid2.txt3
30 files changed, 796 insertions, 171 deletions
diff --git a/.classpath b/.classpath
index 79126ac..e21da51 100644
--- a/.classpath
+++ b/.classpath
@@ -19,6 +19,7 @@
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
+ <classpathentry kind="src" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 732b085..5630737 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -1,5 +1,11 @@
* Changelog
All notable changes in this project will be shown in this file.
+** v0.3.2 - [2021-12-02 Thu]
+*** Added
+ - Added lots more tests for the backend and utilities
+*** Fixed
+ - The UnitDatabase's unit map acted as if it were empty if it had units but no prefixes
+ - Non-exact LinearUnitValue instances had their value printed as "(", meaning "(5.0 ± 0.1) m" would be printed as "( m"
** v0.3.1 - [2021-08-26 Thu]
*** Added
- Created a user manual
diff --git a/README.org b/README.org
index b22a497..2b4ffa0 100644
--- a/README.org
+++ b/README.org
@@ -1,4 +1,4 @@
-* 7Units v0.3.1
+* 7Units v0.3.2
(this project uses Semantic Versioning)
** What is it?
This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in.
diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java
index 2a0edde..31e43c7 100644
--- a/src/main/java/sevenUnits/ProgramInfo.java
+++ b/src/main/java/sevenUnits/ProgramInfo.java
@@ -24,7 +24,7 @@ package sevenUnits;
*/
public final class ProgramInfo {
- public static final String VERSION = "0.3.1";
+ public static final String VERSION = "0.3.2";
private ProgramInfo() {}
diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java
index 81a3f2a..743beeb 100644
--- a/src/main/java/sevenUnits/unit/BritishImperial.java
+++ b/src/main/java/sevenUnits/unit/BritishImperial.java
@@ -37,7 +37,7 @@ public final class BritishImperial {
public static final LinearUnit ROOD = Length.ROD.times(Length.FURLONG);
public static final LinearUnit ACRE = Length.FURLONG.times(Length.CHAIN);
}
-
+
/**
* Imperial units that measure length
*
@@ -46,7 +46,8 @@ public final class BritishImperial {
*/
public static final class Length {
/**
- * According to the International Yard and Pound of 1959, a yard is defined as exactly 0.9144 metres.
+ * According to the International Yard and Pound of 1959, a yard is
+ * defined as exactly 0.9144 metres.
*/
public static final LinearUnit YARD = Metric.METRE.times(0.9144);
public static final LinearUnit FOOT = YARD.dividedBy(3);
@@ -56,15 +57,15 @@ public final class BritishImperial {
public static final LinearUnit FURLONG = CHAIN.times(10);
public static final LinearUnit MILE = FURLONG.times(8);
public static final LinearUnit LEAGUE = MILE.times(3);
-
+
public static final LinearUnit NAUTICAL_MILE = Metric.METRE.times(1852);
public static final LinearUnit CABLE = NAUTICAL_MILE.dividedBy(10);
public static final LinearUnit FATHOM = CABLE.dividedBy(100);
-
+
public static final LinearUnit ROD = YARD.times(5.5);
public static final LinearUnit LINK = ROD.dividedBy(25);
}
-
+
/**
* British Imperial units that measure mass.
*
@@ -82,7 +83,7 @@ public final class BritishImperial {
public static final LinearUnit LONG_TON = HUNDREDWEIGHT.times(20);
public static final LinearUnit SLUG = Metric.KILOGRAM.times(14.59390294);
}
-
+
/**
* British Imperial units that measure volume
*
@@ -90,27 +91,33 @@ public final class BritishImperial {
* @since 2019-11-08
*/
public static final class Volume {
- public static final LinearUnit FLUID_OUNCE = Metric.LITRE.withPrefix(Metric.MILLI).times(28.4130625);
+ public static final LinearUnit FLUID_OUNCE = Metric.LITRE
+ .withPrefix(Metric.MILLI).times(28.4130625);
public static final LinearUnit GILL = FLUID_OUNCE.times(5);
public static final LinearUnit PINT = FLUID_OUNCE.times(20);
public static final LinearUnit QUART = PINT.times(2);
public static final LinearUnit GALLON = QUART.times(4);
public static final LinearUnit PECK = GALLON.times(2);
public static final LinearUnit BUSHEL = PECK.times(4);
-
+
public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3);
public static final LinearUnit CUBIC_FOOT = Length.FOOT.toExponent(3);
public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3);
public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT);
}
-
- public static final LinearUnit OUNCE_FORCE = Mass.OUNCE.times(Metric.Constants.EARTH_GRAVITY);
- public static final LinearUnit POUND_FORCE = Mass.POUND.times(Metric.Constants.EARTH_GRAVITY);
-
- public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE.times(1055.06);
+
+ public static final LinearUnit OUNCE_FORCE = Mass.OUNCE
+ .times(Metric.Constants.EARTH_GRAVITY);
+ public static final LinearUnit POUND_FORCE = Mass.POUND
+ .times(Metric.Constants.EARTH_GRAVITY);
+
+ public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE
+ .times(1055.06);
public static final LinearUnit CALORIE = Metric.JOULE.times(4.184);
public static final LinearUnit KILOCALORIE = Metric.JOULE.times(4184);
-
- public static final Unit FAHRENHEIT = Unit.fromConversionFunctions(Metric.KELVIN.getBase(),
- tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8);
+
+ public static final Unit FAHRENHEIT = Unit
+ .fromConversionFunctions(Metric.KELVIN.getBase(),
+ tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8)
+ .withName(NameSymbol.of("degrees Fahrenheit", "\u00B0F"));
}
diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java
index a36d568..a50e1f5 100644
--- a/src/main/java/sevenUnits/unit/LinearUnitValue.java
+++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java
@@ -324,17 +324,18 @@ public final class LinearUnitValue {
// get rounded strings
// if showUncertainty is true, add brackets around the string
- final String valueString = showUncertainty ? "("
- : "" + this.value.toString(showUncertainty)
- + (showUncertainty ? ")" : "");
- final String baseValueString = showUncertainty ? "("
- : "" + baseValue.toString(showUncertainty)
- + (showUncertainty ? ")" : "");
+ final String valueString = (showUncertainty ? "(" : "")
+ + this.value.toString(showUncertainty)
+ + (showUncertainty ? ")" : "");
+ final String baseValueString = (showUncertainty ? "(" : "")
+ + baseValue.toString(showUncertainty)
+ + (showUncertainty ? ")" : "");
// create string
- if (primaryName.isEmpty() && symbol.isEmpty())
+ if (chosenName == null)
return String.format("%s unnamed unit (= %s %s)", valueString,
- baseValueString, this.unit.getBase());
+ baseValueString, this.unit.getBase()
+ .toString(unit -> unit.getSymbol().orElseThrow()));
else
return String.format("%s %s", valueString, chosenName);
}
diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java
index b45d9cf..18ac619 100644
--- a/src/main/java/sevenUnits/unit/UnitDatabase.java
+++ b/src/main/java/sevenUnits/unit/UnitDatabase.java
@@ -243,7 +243,8 @@ public final class UnitDatabase {
return false;
else {
if (this.prefixNames.isEmpty())
- return this.unitNamePosition >= this.unitNames.size() - 1;
+ return this.prefixCoordinates.isEmpty()
+ && this.unitNamePosition < this.unitNames.size();
else
return true;
}
@@ -557,7 +558,8 @@ public final class UnitDatabase {
return false;
else {
if (this.prefixNames.isEmpty())
- return this.unitNamePosition >= this.unitNames.size() - 1;
+ return this.prefixCoordinates.isEmpty()
+ && this.unitNamePosition < this.unitNames.size();
else
return true;
}
@@ -1038,7 +1040,7 @@ public final class UnitDatabase {
@Override
public String toString() {
if (this.units.isEmpty() || this.prefixes.isEmpty())
- return super.toString();
+ return new HashMap<>(this).toString();
else
return String.format(
"Infinite map of name-unit entries created from units %s and prefixes %s",
@@ -1161,7 +1163,7 @@ public final class UnitDatabase {
* @return true if entry represents a removable duplicate entry of unitMap.
* @since 2021-05-22
*/
- private static boolean isRemovableDuplicate(Map<String, Unit> unitMap,
+ static boolean isRemovableDuplicate(Map<String, Unit> unitMap,
Entry<String, Unit> entry) {
for (final Entry<String, Unit> e : unitMap.entrySet()) {
final String name = e.getKey();
@@ -1344,10 +1346,10 @@ public final class UnitDatabase {
final String name = lineMatcher.group(1);
final String expression = lineMatcher.group(2);
- if (name.endsWith(" ")) {
- System.err.printf("Warning - line %d's dimension name ends in a space",
- lineCounter);
- }
+ // if (name.endsWith(" ")) {
+ // System.err.printf("Warning - line %d's dimension name ends in a space",
+ // lineCounter);
+ // }
// if expression is "!", search for an existing dimension
// if no unit found, throw an error
@@ -1360,7 +1362,7 @@ public final class UnitDatabase {
final ObjectProduct<BaseDimension> dimension;
try {
dimension = this.getDimensionFromExpression(expression);
- } catch (final IllegalArgumentException e) {
+ } catch (final IllegalArgumentException | NoSuchElementException e) {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
@@ -1427,10 +1429,11 @@ public final class UnitDatabase {
final String expression = lineMatcher.group(2);
- if (name.endsWith(" ")) {
- System.err.printf("Warning - line %d's unit name ends in a space",
- lineCounter);
- }
+ // this code should never occur
+ // if (name.endsWith(" ")) {
+ // System.err.printf("Warning - line %d's unit name ends in a space",
+ // lineCounter);
+ // }
// if expression is "!", search for an existing unit
// if no unit found, throw an error
@@ -1443,7 +1446,8 @@ public final class UnitDatabase {
final UnitPrefix prefix;
try {
prefix = this.getPrefixFromExpression(expression);
- } catch (final IllegalArgumentException e) {
+ } catch (final IllegalArgumentException
+ | NoSuchElementException e) {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
@@ -1453,7 +1457,8 @@ public final class UnitDatabase {
final Unit unit;
try {
unit = this.getUnitFromExpression(expression);
- } catch (final IllegalArgumentException e) {
+ } catch (final IllegalArgumentException
+ | NoSuchElementException e) {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
@@ -1581,8 +1586,15 @@ public final class UnitDatabase {
}
return base.toExponent(exponent);
+ } else {
+ final ObjectProduct<BaseDimension> dimension = this.dimensions
+ .get(name);
+ if (dimension == null)
+ throw new NoSuchElementException(
+ "No dimension with name \"" + name + "\".");
+ else
+ return dimension;
}
- return this.dimensions.get(name);
}
/**
@@ -1635,7 +1647,7 @@ public final class UnitDatabase {
* @since 2019-03-22
* @since v0.2.0
*/
- private LinearUnit getLinearUnit(final String name) {
+ LinearUnit getLinearUnit(final String name) {
// see if I am using a function-unit like tempC(100)
Objects.requireNonNull(name, "name may not be null");
if (name.contains("(") && name.contains(")")) {
@@ -1670,7 +1682,7 @@ public final class UnitDatabase {
* @return {@code LinearUnitValue} instance
* @since 2020-08-04
*/
- private LinearUnitValue getLinearUnitValue(final String name) {
+ LinearUnitValue getLinearUnitValue(final String name) {
try {
// try to parse it as a number - otherwise it is not a number!
final BigDecimal number = new BigDecimal(name);
@@ -1695,7 +1707,12 @@ public final class UnitDatabase {
try {
return UnitPrefix.valueOf(Double.parseDouble(name));
} catch (final NumberFormatException e) {
- return this.prefixes.get(name);
+ final UnitPrefix prefix = this.prefixes.get(name);
+ if (prefix == null)
+ throw new NoSuchElementException(
+ "No prefix with name \"" + name + "\".");
+ else
+ return prefix;
}
}
diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java
index 2fedd81..f6d18f8 100644
--- a/src/main/java/sevenUnits/unit/UnitValue.java
+++ b/src/main/java/sevenUnits/unit/UnitValue.java
@@ -163,7 +163,8 @@ public final class UnitValue {
if (primaryName.isEmpty() && symbol.isEmpty()) {
final double baseValue = this.getUnit().convertToBase(this.getValue());
return String.format("%s unnamed unit (= %s %s)", this.getValue(),
- baseValue, this.getUnit().getBase());
+ baseValue, this.getUnit().getBase()
+ .toString(unit -> unit.getSymbol().orElseThrow()));
} else {
final String unitName = symbol.orElse(primaryName.get());
return this.getValue() + " " + unitName;
diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java
index 2adb579..bee4dd1 100644
--- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java
+++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java
@@ -283,7 +283,7 @@ public final class ConditionalExistenceCollections {
@Override
public Set<K> keySet() {
- return conditionalExistenceSet(super.keySet(),
+ return conditionalExistenceSet(this.map.keySet(),
k -> this.entryExistenceCondition.test(this.getEntry(k)));
}
diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java
index 1d3d44d..941c2a4 100644
--- a/src/main/java/sevenUnits/utils/ExpressionParser.java
+++ b/src/main/java/sevenUnits/utils/ExpressionParser.java
@@ -461,10 +461,12 @@ public final class ExpressionParser<T> {
*
* @param expression expression
* @return expression in RPN
+ * @throws IllegalArgumentException if expression is invalid (e.g.
+ * "{@code 3 *}")
* @since 2019-03-17
* @since v0.2.0
*/
- private String convertExpressionToReversePolish(final String expression) {
+ String convertExpressionToReversePolish(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
final List<String> components = new ArrayList<>();
@@ -541,6 +543,9 @@ public final class ExpressionParser<T> {
switch (this
.getTokenType(components.get(highestPriorityOperatorPosition))) {
case UNARY_OPERATOR:
+ if (components.size() < 2)
+ throw new IllegalArgumentException(
+ "Invalid expression \"" + expression + "\"");
final String unaryOperator = components
.remove(highestPriorityOperatorPosition);
final String operand = components
@@ -549,6 +554,9 @@ public final class ExpressionParser<T> {
operand + " " + unaryOperator);
break;
case BINARY_OPERATOR:
+ if (components.size() < 3)
+ throw new IllegalArgumentException(
+ "Invalid expression \"" + expression + "\"");
final String binaryOperator = components
.remove(highestPriorityOperatorPosition);
final String operand1 = components
@@ -672,7 +680,7 @@ public final class ExpressionParser<T> {
* @since 2019-03-14
* @since v0.2.0
*/
- private T parseReversePolishExpression(final String expression) {
+ T parseReversePolishExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
final Deque<T> stack = new ArrayDeque<>();
diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java
index 1dacb7d..5b1b739 100644
--- a/src/main/java/sevenUnits/utils/ObjectProduct.java
+++ b/src/main/java/sevenUnits/utils/ObjectProduct.java
@@ -27,8 +27,8 @@ import java.util.Set;
import java.util.function.Function;
/**
- * An immutable product of multiple objects of a type, such as base units. The objects can be multiplied and
- * exponentiated.
+ * An immutable product of multiple objects of a type, such as base units. The
+ * objects can be multiplied and exponentiated.
*
* @author Adrien Hopkins
* @since 2019-10-16
@@ -37,38 +37,35 @@ public final class ObjectProduct<T> {
/**
* Returns an empty ObjectProduct of a certain type
*
- * @param <T>
- * type of objects that can be multiplied
+ * @param <T> type of objects that can be multiplied
* @return empty product
* @since 2019-10-16
*/
public static final <T> ObjectProduct<T> empty() {
return new ObjectProduct<>(new HashMap<>());
}
-
+
/**
* Gets an {@code ObjectProduct} from an object-to-integer mapping
*
- * @param <T>
- * type of object in product
- * @param map
- * map mapping objects to exponents
+ * @param <T> type of object in product
+ * @param map map mapping objects to exponents
* @return object product
* @since 2019-10-16
*/
- public static final <T> ObjectProduct<T> fromExponentMapping(final Map<T, Integer> map) {
+ public static final <T> ObjectProduct<T> fromExponentMapping(
+ final Map<T, Integer> map) {
return new ObjectProduct<>(new HashMap<>(map));
}
-
+
/**
- * Gets an ObjectProduct that has one of the inputted argument, and nothing else.
+ * Gets an ObjectProduct that has one of the inputted argument, and nothing
+ * else.
*
- * @param object
- * object that will be in the product
+ * @param object object that will be in the product
* @return product
* @since 2019-10-16
- * @throws NullPointerException
- * if object is null
+ * @throws NullPointerException if object is null
*/
public static final <T> ObjectProduct<T> oneOf(final T object) {
Objects.requireNonNull(object, "object must not be null.");
@@ -76,35 +73,34 @@ public final class ObjectProduct<T> {
map.put(object, 1);
return new ObjectProduct<>(map);
}
-
+
/**
- * The objects that make up the product, mapped to their exponents. This map treats zero as null, and is immutable.
+ * The objects that make up the product, mapped to their exponents. This map
+ * treats zero as null, and is immutable.
*
* @since 2019-10-16
*/
final Map<T, Integer> exponents;
-
+
/**
* Creates the {@code ObjectProduct}.
*
- * @param exponents
- * objects that make up this product
+ * @param exponents objects that make up this product
* @since 2019-10-16
*/
private ObjectProduct(final Map<T, Integer> exponents) {
- this.exponents = Collections.unmodifiableMap(ConditionalExistenceCollections.conditionalExistenceMap(exponents,
- e -> !Integer.valueOf(0).equals(e.getValue())));
+ this.exponents = Collections.unmodifiableMap(
+ ConditionalExistenceCollections.conditionalExistenceMap(exponents,
+ e -> !Integer.valueOf(0).equals(e.getValue())));
}
-
+
/**
* Calculates the quotient of two products
*
- * @param other
- * other product
+ * @param other other product
* @return quotient of two products
* @since 2019-10-16
- * @throws NullPointerException
- * if other is null
+ * @throws NullPointerException if other is null
*/
public ObjectProduct<T> dividedBy(final ObjectProduct<T> other) {
Objects.requireNonNull(other, "other must not be null.");
@@ -112,17 +108,17 @@ public final class ObjectProduct<T> {
final Set<T> objects = new HashSet<>();
objects.addAll(this.getBaseSet());
objects.addAll(other.getBaseSet());
-
+
// get a list of all exponents
final Map<T, Integer> map = new HashMap<>(objects.size());
for (final T key : objects) {
map.put(key, this.getExponent(key) - other.getExponent(key));
}
-
+
// create the product
return new ObjectProduct<>(map);
}
-
+
// this method relies on the use of ZeroIsNullMap
@Override
public boolean equals(final Object obj) {
@@ -133,7 +129,7 @@ public final class ObjectProduct<T> {
final ObjectProduct<?> other = (ObjectProduct<?>) obj;
return Objects.equals(this.exponents, other.exponents);
}
-
+
/**
* @return immutable map mapping objects to exponents
* @since 2019-10-16
@@ -141,30 +137,31 @@ public final class ObjectProduct<T> {
public Map<T, Integer> exponentMap() {
return this.exponents;
}
-
+
/**
- * @return a set of all of the base objects with non-zero exponents that make up this dimension.
+ * @return a set of all of the base objects with non-zero exponents that make
+ * up this dimension.
* @since 2018-12-12
* @since v0.1.0
*/
public final Set<T> getBaseSet() {
final Set<T> dimensions = new HashSet<>();
-
- // add all dimensions with a nonzero exponent - zero exponents shouldn't be there in the first place
+
+ // add all dimensions with a nonzero exponent - zero exponents shouldn't
+ // be there in the first place
for (final T dimension : this.exponents.keySet()) {
if (!this.exponents.get(dimension).equals(0)) {
dimensions.add(dimension);
}
}
-
+
return dimensions;
}
-
+
/**
* Gets the exponent for a specific dimension.
*
- * @param dimension
- * dimension to check
+ * @param dimension dimension to check
* @return exponent for that dimension
* @since 2018-12-12
* @since v0.1.0
@@ -172,14 +169,15 @@ public final class ObjectProduct<T> {
public int getExponent(final T dimension) {
return this.exponents.getOrDefault(dimension, 0);
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.exponents);
}
-
+
/**
- * @return true if this product is a single object, i.e. it has one exponent of one and no other nonzero exponents
+ * @return true if this product is a single object, i.e. it has one exponent
+ * of one and no other nonzero exponents
* @since 2019-10-16
*/
public boolean isSingleObject() {
@@ -194,16 +192,14 @@ public final class ObjectProduct<T> {
}
return oneCount == 1 && !twoOrMore;
}
-
+
/**
* Multiplies this product by another
*
- * @param other
- * other product
+ * @param other other product
* @return product of two products
* @since 2019-10-16
- * @throws NullPointerException
- * if other is null
+ * @throws NullPointerException if other is null
*/
public ObjectProduct<T> times(final ObjectProduct<T> other) {
Objects.requireNonNull(other, "other must not be null.");
@@ -211,22 +207,21 @@ public final class ObjectProduct<T> {
final Set<T> objects = new HashSet<>();
objects.addAll(this.getBaseSet());
objects.addAll(other.getBaseSet());
-
+
// get a list of all exponents
final Map<T, Integer> map = new HashMap<>(objects.size());
for (final T key : objects) {
map.put(key, this.getExponent(key) + other.getExponent(key));
}
-
+
// create the product
return new ObjectProduct<>(map);
}
-
+
/**
* Returns this product, but to an exponent
*
- * @param exponent
- * exponent
+ * @param exponent exponent
* @return result of exponentiation
* @since 2019-10-16
*/
@@ -237,11 +232,12 @@ public final class ObjectProduct<T> {
}
return new ObjectProduct<>(map);
}
-
+
/**
- * Converts this product to a string using the objects' {@link Object#toString()} method. If objects have a long
- * toString representation, it is recommended to use {@link #toString(Function)} instead to shorten the returned
- * string.
+ * Converts this product to a string using the objects'
+ * {@link Object#toString()} method. If objects have a long toString
+ * representation, it is recommended to use {@link #toString(Function)}
+ * instead to shorten the returned string.
*
* <p>
* {@inheritDoc}
@@ -250,35 +246,38 @@ public final class ObjectProduct<T> {
public String toString() {
return this.toString(Object::toString);
}
-
+
/**
- * Converts this product to a string. The objects that make up this product are represented by
- * {@code objectToString}
+ * Converts this product to a string. The objects that make up this product
+ * are represented by {@code objectToString}
*
- * @param objectToString
- * function to convert objects to strings
+ * @param objectToString function to convert objects to strings
* @return string representation of product
* @since 2019-10-16
*/
public String toString(final Function<T, String> objectToString) {
final List<String> positiveStringComponents = new ArrayList<>();
final List<String> negativeStringComponents = new ArrayList<>();
-
+
// for each base object that makes up this object, add it and its exponent
for (final T object : this.getBaseSet()) {
final int exponent = this.exponents.get(object);
- if (exponent > 0) {
- positiveStringComponents.add(String.format("%s^%d", objectToString.apply(object), exponent));
+ if (exponent > 1) {
+ positiveStringComponents.add(String.format("%s^%d",
+ objectToString.apply(object), exponent));
+ } else if (exponent == 1) {
+ positiveStringComponents.add(objectToString.apply(object));
} else if (exponent < 0) {
- negativeStringComponents.add(String.format("%s^%d", objectToString.apply(object), -exponent));
+ negativeStringComponents.add(String.format("%s^%d",
+ objectToString.apply(object), -exponent));
}
}
-
+
final String positiveString = positiveStringComponents.isEmpty() ? "1"
: String.join(" * ", positiveStringComponents);
final String negativeString = negativeStringComponents.isEmpty() ? ""
: " / " + String.join(" * ", negativeStringComponents);
-
+
return positiveString + negativeString;
}
}
diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java
index 8fe4b31..fe41104 100644
--- a/src/main/java/sevenUnits/utils/UncertainDouble.java
+++ b/src/main/java/sevenUnits/utils/UncertainDouble.java
@@ -36,13 +36,14 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
*/
public static final UncertainDouble ZERO = UncertainDouble.of(0, 0);
+ static final String NUMBER_REGEX = "(\\d+(?:[\\.,]\\d+))";
+
/**
* A regular expression that can recognize toString forms
*/
- private static final Pattern TO_STRING = Pattern
- .compile("([a-zA-Z_0-9\\.\\,]+)" // a number
- // optional "± [number]"
- + "(?:\\s*(?:±|\\+-)\\s*([a-zA-Z_0-9\\.\\,]+))?");
+ static final Pattern TO_STRING = Pattern.compile(NUMBER_REGEX
+ // optional "± [number]"
+ + "(?:\\s*(?:±|\\+-)\\s*" + NUMBER_REGEX + ")?");
/**
* Parses a string in the form of {@link UncertainDouble#toString(boolean)}
@@ -60,10 +61,14 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
Objects.requireNonNull(s, "s may not be null");
final Matcher matcher = TO_STRING.matcher(s);
+ if (!matcher.matches())
+ throw new IllegalArgumentException(
+ "Could not parse stirng \"" + s + "\".");
+
double value, uncertainty;
try {
value = Double.parseDouble(matcher.group(1));
- } catch (IllegalStateException | NumberFormatException e) {
+ } catch (final NumberFormatException e) {
throw new IllegalArgumentException(
"String " + s + " not in correct format.");
}
diff --git a/src/test/java/sevenUnits/unit/MultiUnitTest.java b/src/test/java/sevenUnits/unit/MultiUnitTest.java
index d632118..39ee21c 100644
--- a/src/test/java/sevenUnits/unit/MultiUnitTest.java
+++ b/src/test/java/sevenUnits/unit/MultiUnitTest.java
@@ -25,10 +25,6 @@ import java.util.concurrent.ThreadLocalRandom;
import org.junit.jupiter.api.Test;
-import sevenUnits.unit.BritishImperial;
-import sevenUnits.unit.MultiUnit;
-import sevenUnits.unit.Metric;
-
/**
* Tests related to the {@code MultiUnit}.
*
diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java
index 1d5e503..2276d7c 100644
--- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java
+++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java
@@ -20,23 +20,26 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Set;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
-import sevenUnits.unit.LinearUnit;
-import sevenUnits.unit.NameSymbol;
-import sevenUnits.unit.Metric;
-import sevenUnits.unit.Unit;
-import sevenUnits.unit.UnitDatabase;
-import sevenUnits.unit.UnitPrefix;
+import sevenUnits.utils.UncertainDouble;
/**
* A test for the {@link UnitDatabase} class. This is NOT part of this program's
@@ -47,20 +50,70 @@ import sevenUnits.unit.UnitPrefix;
* @since v0.2.0
*/
class UnitDatabaseTest {
+ private static final class SimpleEntry<K, V> implements Map.Entry<K, V> {
+ private final K key;
+
+ private V value;
+
+ /**
+ *
+ * @since 2021-10-07
+ */
+ public SimpleEntry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof Map.Entry<?, ?>))
+ return false;
+ final Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
+ return Objects.equals(this.key, other.getKey())
+ && Objects.equals(this.value, other.getValue());
+ }
+
+ @Override
+ public K getKey() {
+ return this.key;
+ }
+
+ @Override
+ public V getValue() {
+ return this.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return (this.key == null ? 0 : this.key.hashCode())
+ ^ (this.value == null ? 0 : this.value.hashCode());
+ }
+
+ @Override
+ public V setValue(V value) {
+ final V oldValue = this.value;
+ this.value = value;
+ return oldValue;
+ }
+ }
+
// some linear units and one nonlinear
private static final Unit U = Metric.METRE;
private static final Unit V = Metric.KILOGRAM;
- private static final Unit W = Metric.SECOND;
+ private static final Unit W = Metric.SECOND;
// used for testing expressions
// J = U^2 * V / W^2
- private static final LinearUnit J = Metric.KILOGRAM.times(Metric.METRE.toExponent(2))
+ private static final LinearUnit J = Metric.KILOGRAM
+ .times(Metric.METRE.toExponent(2))
.dividedBy(Metric.SECOND.toExponent(2));
- private static final LinearUnit K = Metric.KELVIN;
- private static final Unit NONLINEAR = Unit
- .fromConversionFunctions(Metric.METRE.getBase(), o -> o + 1, o -> o - 1);
+ private static final LinearUnit K = Metric.KELVIN;
+ private static final Unit NONLINEAR = Unit.fromConversionFunctions(
+ Metric.METRE.getBase(), o -> o + 1, o -> o - 1);
// make the prefix values prime so I can tell which multiplications were made
private static final UnitPrefix A = UnitPrefix.valueOf(2)
.withName(NameSymbol.ofName("A"));
@@ -69,9 +122,120 @@ class UnitDatabaseTest {
private static final UnitPrefix C = UnitPrefix.valueOf(5)
.withName(NameSymbol.ofName("C"));
private static final UnitPrefix AB = UnitPrefix.valueOf(7);
+
private static final UnitPrefix BC = UnitPrefix.valueOf(11);
/**
+ * Gets a map entry.
+ *
+ * @param <K> type of key
+ * @param <V> type of value
+ * @param key key in entry
+ * @param value value in entry
+ * @return entry
+ * @since 2021-10-07
+ */
+ private static <K, V> Map.Entry<K, V> entry(K key, V value) {
+ return new SimpleEntry<>(key, value);
+ }
+
+ /**
+ * Loads the dimensionfile at src/test/resources/[path] to the database
+ * {@code loadTo}.
+ *
+ * @param loadTo database to load to
+ * @param path path of file to load
+ * @since 2021-10-04
+ */
+ private static void loadDimensionFile(UnitDatabase loadTo, String path) {
+ try (final InputStream testFile = UnitDatabaseTest.class
+ .getResourceAsStream(path)) {
+ loadTo.loadDimensionsFromStream(testFile);
+ } catch (final IOException e) {
+ fail(e.getClass() + " occurred upon loading file \"" + path + "\".");
+ }
+ }
+
+ /**
+ * Loads the unitfile at src/test/resources/[path] to the database
+ * {@code loadTo}.
+ *
+ * @param loadTo database to load to
+ * @param path path of file to load
+ * @since 2021-09-22
+ */
+ private static void loadUnitsFile(UnitDatabase loadTo, String path) {
+ try (final InputStream testFile = UnitDatabaseTest.class
+ .getResourceAsStream(path)) {
+ loadTo.loadUnitsFromStream(testFile);
+ } catch (final IOException e) {
+ fail(e.getClass() + " occurred upon loading file \"" + path + "\".");
+ }
+ }
+
+ /**
+ * A test for the {@link UnitDatabase#evaluateUnitExpression(String)}
+ * function. Simple because the expression parser has its own test.
+ *
+ * @since 2021-09-27
+ */
+ @Test
+ public void testEvaluateExpression() {
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("J", J);
+ database.addUnit("K", K);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ final LinearUnitValue expected = LinearUnitValue.of(J,
+ UncertainDouble.of(12, Math.sqrt(14.625)));
+ // note: units are exact, each number has an uncertainty of 1
+ final LinearUnitValue actual = database
+ .evaluateUnitExpression("J + (2 * 3) J + (20 / 4) J");
+ assertEquals(expected, actual);
+
+ // check that negation works properly
+ assertEquals(2,
+ database.evaluateUnitExpression("J - -1 * J").getValueExact());
+ }
+
+ /**
+ * Test for {@link UnitDatabase#getUnit}, {@link UnitDatabase#getLinearUnit}
+ * and {@link UnitDatabase#getLinearUnitValue}.
+ *
+ * @since 2021-10-07
+ */
+ @Test
+ public void testGetUnit() {
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("m", Metric.METRE);
+ database.addUnit("meter", Metric.METRE);
+ database.addUnit("metre", Metric.METRE);
+ database.addUnit("badname", Metric.METRE);
+ database.addUnit("K", Metric.KELVIN);
+ database.addUnit("degC", Metric.CELSIUS);
+
+ // ensure getUnit returns units, regardless of whether the name is one of
+ // the unit's names
+ assertEquals(Metric.METRE, database.getUnit("m"));
+ assertEquals(Metric.METRE, database.getUnit("metre"));
+ assertEquals(Metric.METRE, database.getUnit("meter"));
+ assertEquals(Metric.METRE, database.getUnit("badname"));
+ assertThrows(NoSuchElementException.class,
+ () -> database.getUnit("blabla"));
+
+ assertEquals(Metric.KELVIN, database.getLinearUnit("K"));
+ assertThrows(IllegalArgumentException.class,
+ () -> database.getLinearUnit("degC"));
+ assertEquals(Metric.KELVIN.times(373.15),
+ database.getLinearUnit("degC(100)"));
+ }
+
+ /**
* Confirms that operations that shouldn't function for infinite databases
* throw an {@code IllegalStateException}.
*
@@ -98,6 +262,160 @@ class UnitDatabaseTest {
}
/**
+ * A bunch of tests for invalid dimension files
+ *
+ * @param num which file to test
+ * @since 2021-10-04
+ */
+ @ParameterizedTest
+ @ValueSource(ints = { 1, 2, 3 })
+ public void testLoadingInvalidDimensionFile(int num) {
+ final UnitDatabase database = new UnitDatabase();
+ database.addDimension("LENGTH", Metric.Dimensions.LENGTH);
+ database.addDimension("MASS", Metric.Dimensions.MASS);
+ database.addDimension("TIME", Metric.Dimensions.TIME);
+ final String filename = String.format("/test-dimensionfile-invalid%d.txt",
+ num);
+ final RuntimeException e = assertThrows(RuntimeException.class,
+ () -> loadDimensionFile(database, filename));
+ assertTrue(e instanceof IllegalArgumentException
+ || e instanceof NoSuchElementException);
+ }
+
+ /**
+ * A bunch of tests for invalid unit files
+ *
+ * @param num which file to test
+ * @since 2021-09-27
+ */
+ @ParameterizedTest
+ @ValueSource(ints = { 1, 2, 3, 4, 5 })
+ public void testLoadingInvalidUnitFile(int num) {
+ final UnitDatabase database = new UnitDatabase();
+ final String filename = String.format("/test-unitsfile-invalid%d.txt",
+ num);
+ final RuntimeException e = assertThrows(RuntimeException.class,
+ () -> loadUnitsFile(database, filename));
+ assertTrue(e instanceof IllegalArgumentException
+ || e instanceof NoSuchElementException);
+ }
+
+ /**
+ * Tests loading a valid dimension-file with some derived dimensions.
+ *
+ * @since 2021-10-04
+ */
+ @Test
+ public void testLoadingValidDimensions() {
+ final UnitDatabase database = new UnitDatabase();
+ database.addDimension("LENGTH", Metric.Dimensions.LENGTH);
+ database.addDimension("MASS", Metric.Dimensions.MASS);
+ database.addDimension("TIME", Metric.Dimensions.TIME);
+
+ loadDimensionFile(database, "/test-dimensionfile-valid1.txt");
+ assertEquals(Metric.Dimensions.ENERGY, database.getDimension("ENERGY"));
+ assertEquals(Metric.Dimensions.POWER, database.getDimension("POWER"));
+
+ }
+
+ /**
+ * Tests loading a valid unitfile with some prefixes and no units.
+ *
+ * @since 2021-09-22
+ */
+ @Test
+ public void testLoadingValidPrefixes() {
+ final UnitDatabase database = new UnitDatabase();
+
+ loadUnitsFile(database, "/test-unitsfile-valid2.txt");
+ assertEquals(7, database.getPrefix("A").getMultiplier());
+ assertEquals(11, database.getPrefix("B").getMultiplier());
+ assertEquals(13, database.getPrefix("C").getMultiplier());
+ }
+
+ /**
+ * Tests loading a valid unitfile with some units and preloaded prefixes
+ *
+ * @since 2021-09-22
+ */
+ @Test
+ public void testLoadingValidUnits() {
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+ database.addUnit("fj", J.times(5));
+ database.addUnit("ej", J.times(8));
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ loadUnitsFile(database, "/test-unitsfile-valid1.txt");
+
+ final Unit expected1 = ((LinearUnit) U).withPrefix(A).withPrefix(B)
+ .withPrefix(C);
+ final Unit actual1 = database.getUnit("test1");
+ assertEquals(expected1, actual1);
+
+ final Unit expected2 = ((LinearUnit) W).withPrefix(B)
+ .times(((LinearUnit) V).withPrefix(C));
+ final Unit actual2 = database.getUnit("test2");
+ assertEquals(expected2, actual2);
+
+ final Unit expected3 = ((LinearUnit) U)
+ .times(A.getMultiplier() + C.getMultiplier() - B.getMultiplier());
+ final Unit actual3 = database.getUnit("test3");
+ assertEquals(expected3, actual3);
+
+ final UnitValue expected4 = UnitValue.of(U, 1);
+ final UnitValue actual4 = database
+ .evaluateUnitExpression("-5 * U + -3 * U + 12 * U - 3 * U")
+ .asUnitValue();
+ assertEquals(expected4, actual4);
+
+ assertTrue(System.err.toString().length() > 0);
+ }
+
+ /**
+ * Tests the iterator of the prefixless unit map. These tests are simple, as
+ * the unit map iterator is simple.
+ *
+ * @since 2021-10-07
+ */
+ @Test
+ public void testPrefixedUnitMapIterator() {
+ final UnitDatabase database1 = new UnitDatabase();
+
+ database1.addUnit("U", U);
+ database1.addUnit("V", V);
+ database1.addUnit("W", W);
+
+ final Map<String, Unit> map1 = database1.unitMap();
+ final Iterator<String> keyIterator1 = map1.keySet().iterator();
+ final Iterator<Map.Entry<String, Unit>> entryIterator1 = map1.entrySet()
+ .iterator();
+
+ final Set<String> expectedKeys = Set.of("U", "V", "W");
+ final Set<String> actualKeys = new HashSet<>();
+ while (keyIterator1.hasNext()) {
+ actualKeys.add(keyIterator1.next());
+ }
+ assertEquals(expectedKeys, actualKeys);
+ assertEquals(expectedKeys, map1.keySet());
+
+ final Set<Map.Entry<String, Unit>> expectedEntries = Set.of(entry("U", U),
+ entry("V", V), entry("W", W));
+ final Set<Map.Entry<String, Unit>> actualEntries = new HashSet<>();
+ while (entryIterator1.hasNext()) {
+ actualEntries.add(entryIterator1.next());
+ }
+ assertEquals(expectedEntries, actualEntries);
+ assertEquals(expectedEntries, map1.entrySet());
+ }
+
+ /**
* Test that prefixes correctly apply to units.
*
* @since 2019-04-14
@@ -178,6 +496,45 @@ class UnitDatabaseTest {
assertThrows(NoSuchElementException.class, () -> database.getUnit("Z"));
}
+ @Test
+ public void testRemovableDuplicates() {
+ final Map<String, Unit> unitMap = new HashMap<>();
+ unitMap.put("meter", Metric.METRE);
+ unitMap.put("metre", Metric.METRE);
+ unitMap.put("m", Metric.METRE);
+ unitMap.put("second", Metric.SECOND);
+
+ assertTrue(UnitDatabase.isRemovableDuplicate(unitMap,
+ entry("m", Metric.METRE)));
+ assertTrue(UnitDatabase.isRemovableDuplicate(unitMap,
+ entry("meter", Metric.METRE)));
+ assertFalse(UnitDatabase.isRemovableDuplicate(unitMap,
+ entry("metre", Metric.METRE)));
+ assertFalse(UnitDatabase.isRemovableDuplicate(unitMap,
+ entry("second", Metric.SECOND)));
+ }
+
+ @Test
+ public void testToString() {
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("J", J);
+ database.addUnit("K", J);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ if ("Unit Database with 1 units, 3 unit prefixes and 0 dimensions"
+ .equals(database.toString())) {
+ fail("Database counts by number of units, not number of unit names.");
+ }
+
+ assertEquals(
+ "Unit Database with 2 units, 3 unit prefixes and 0 dimensions",
+ database.toString());
+ }
+
/**
* Test that unit expressions return the correct value.
*
@@ -211,6 +568,12 @@ class UnitDatabaseTest {
final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej");
assertEquals(expected2, actual2);
+
+ // test incorrect expressions
+ assertThrows(IllegalArgumentException.class,
+ () -> database.getUnitFromExpression("U + V"));
+ assertThrows(IllegalArgumentException.class,
+ () -> database.getUnitFromExpression("U - V"));
}
/**
diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java
index a980054..bb2e6a4 100644
--- a/src/test/java/sevenUnits/unit/UnitTest.java
+++ b/src/test/java/sevenUnits/unit/UnitTest.java
@@ -26,13 +26,8 @@ import java.util.concurrent.ThreadLocalRandom;
import org.junit.jupiter.api.Test;
-import sevenUnits.unit.LinearUnit;
-import sevenUnits.unit.LinearUnitValue;
-import sevenUnits.unit.NameSymbol;
-import sevenUnits.unit.Metric;
-import sevenUnits.unit.Unit;
-import sevenUnits.unit.UnitValue;
import sevenUnits.utils.DecimalComparison;
+import sevenUnits.utils.UncertainDouble;
/**
* Testing the various Unit classes. This is NOT part of this program's public
@@ -59,8 +54,10 @@ class UnitTest {
// test with LinearUnitValue
final LinearUnitValue value1 = LinearUnitValue.getExact(Metric.METRE, 15);
final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120);
- final LinearUnitValue value3 = LinearUnitValue.getExact(Metric.METRE, 0.5);
- final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM, 60);
+ final LinearUnitValue value3 = LinearUnitValue.getExact(Metric.METRE,
+ 0.5);
+ final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM,
+ 60);
// make sure addition is done correctly
assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001);
@@ -77,6 +74,7 @@ class UnitTest {
// make sure errors happen when they should
assertThrows(IllegalArgumentException.class, () -> value1.plus(value4));
+ assertThrows(IllegalArgumentException.class, () -> value1.minus(value4));
}
@Test
@@ -128,7 +126,8 @@ class UnitTest {
public void testMultiplicationAndDivision() {
// test unit-times-unit multiplication
final LinearUnit generatedJoule = Metric.KILOGRAM
- .times(Metric.METRE.toExponent(2)).dividedBy(Metric.SECOND.toExponent(2));
+ .times(Metric.METRE.toExponent(2))
+ .dividedBy(Metric.SECOND.toExponent(2));
final LinearUnit actualJoule = Metric.JOULE;
assertEquals(generatedJoule, actualJoule);
@@ -138,16 +137,87 @@ class UnitTest {
final LinearUnit hour = Metric.SECOND.times(3600);
final LinearUnit generatedKPH = kilometre.dividedBy(hour);
- final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND).dividedBy(3.6);
+ final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND)
+ .dividedBy(3.6);
assertEquals(generatedKPH, actualKPH);
}
@Test
public void testPrefixes() {
- final LinearUnit generatedKilometre = Metric.METRE.withPrefix(Metric.KILO);
+ final LinearUnit generatedKilometre = Metric.METRE
+ .withPrefix(Metric.KILO);
final LinearUnit actualKilometre = Metric.METRE.times(1000);
assertEquals(generatedKilometre, actualKilometre);
}
+
+ /**
+ * Tests converting an uncertain LinearUnitValue to a string.
+ *
+ * @since 2021-11-04
+ */
+ @Test
+ public void testValueToString1() {
+ final LinearUnitValue value = LinearUnitValue.of(Metric.METRE,
+ UncertainDouble.of(10, 0.24));
+
+ assertEquals("(10.0 ± 0.2) m", value.toString());
+ assertEquals("(10.0 ± 0.2) m", value.toString(true));
+ assertEquals("10.0 m", value.toString(false));
+ }
+
+ /**
+ * Tests converting a certain LinearUnitValue to a string.
+ *
+ * @since 2021-11-04
+ */
+ @Test
+ public void testValueToString2() {
+ final LinearUnitValue value = LinearUnitValue.of(Metric.METRE,
+ UncertainDouble.of(10, 0));
+
+ assertEquals("10.0 m", value.toString());
+ assertEquals("(10.0 ± 0.0) m", value.toString(true));
+ assertEquals("10.0 m", value.toString(false));
+ }
+
+ /**
+ * Tests converting an unnamed LinearUnitValue to a string.
+ *
+ * @since 2021-11-04
+ */
+ @Test
+ public void testValueToString3() {
+ final LinearUnitValue value = LinearUnitValue.of(
+ Metric.METRE.withName(NameSymbol.EMPTY),
+ UncertainDouble.of(10, 0.24));
+
+ assertEquals("10.0 unnamed unit (= 10.0 m)", value.toString(false));
+ }
+
+ /**
+ * Tests converting a named UnitValue to a string.
+ *
+ * @since 2021-11-04
+ */
+ @Test
+ public void testValueToString4() {
+ final UnitValue value = UnitValue.of(BritishImperial.FAHRENHEIT, 80);
+
+ assertEquals("80.0 \u00B0F", value.toString());
+ }
+
+ /**
+ * Tests converting an unnamed UnitValue to a string.
+ *
+ * @since 2021-11-04
+ */
+ @Test
+ public void testValueToString5() {
+ final UnitValue value = UnitValue
+ .of(USCustomary.FAHRENHEIT.withName(NameSymbol.EMPTY), 50);
+
+ assertEquals("50.0 unnamed unit (= 283.15 K)", value.toString());
+ }
}
diff --git a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java
index 46afe77..d653848 100644
--- a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java
+++ b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java
@@ -31,7 +31,6 @@ import java.util.NoSuchElementException;
import org.junit.jupiter.api.Test;
-import sevenUnits.utils.ConditionalExistenceCollections;
import sevenUnits.utils.ConditionalExistenceCollections.ConditionalExistenceIterator;
/**
diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java
index 29648ee..a954b12 100644
--- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java
+++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java
@@ -18,37 +18,65 @@ package sevenUnits.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import org.junit.jupiter.api.Test;
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
-import sevenUnits.utils.ExpressionParser;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+// TODO add tests for expression-to-RPN and RPN-to-result
/**
- * A test for the {@code ExpressionParser} class. This is NOT part of this program's public API.
+ * A test for the {@code ExpressionParser} class. This is NOT part of this
+ * program's public API.
*
* @author Adrien Hopkins
* @since 2019-03-22
* @since v0.2.0
*/
class ExpressionParserTest {
- private static final ExpressionParser<Integer> numberParser = new ExpressionParser.Builder<>(Integer::parseInt)
- .addBinaryOperator("+", (o1, o2) -> o1 + o2, 0).addBinaryOperator("-", (o1, o2) -> o1 - o2, 0)
- .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1).addBinaryOperator("/", (o1, o2) -> o1 / o2, 1)
- .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build();
-
+ private static final ExpressionParser<Integer> numberParser = new ExpressionParser.Builder<>(
+ Integer::parseInt).addBinaryOperator("+", (o1, o2) -> o1 + o2, 0)
+ .addBinaryOperator("-", (o1, o2) -> o1 - o2, 0)
+ .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1)
+ .addBinaryOperator("/", (o1, o2) -> o1 / o2, 1)
+ .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2)
+ .build();
+
/**
- * Test method for {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}.
+ * The expressions used in the expression parsing tests
*/
- @Test
- public void testParseExpression() {
- // test parsing of expressions
- assertEquals((int) numberParser.parseExpression("1 + 2 ^ 5 * 3"), 97);
- assertEquals((int) numberParser.parseExpression("(1 + 2) ^ 5 * 3"), 729);
-
- // ensure it normally goes left to right
- assertEquals((int) numberParser.parseExpression("1 + 2 + 3 + 4"), 10);
- assertEquals((int) numberParser.parseExpression("12 - 4 - 3"), 5);
- assertEquals((int) numberParser.parseExpression("12 - (4 - 3)"), 11);
- assertEquals((int) numberParser.parseExpression("1 / 2 + 3"), 3);
+ private static final List<String> TEST_EXPRESSIONS = List.of(
+ // test parsing of expressions
+ "1 + 2 ^ 5 * 3", "(1 + 2) ^ 5 * 3",
+ "12 * 5 + (3 ^ (2 * 3) - 72) / (3 + 3 * 2)",
+
+ // ensure it normally goes from left to right
+ "1 + 2 + 3 + 4", "12 - 4 - 3", "12 - (4 - 3)", "1 / 2 + 3");
+
+ /**
+ * The expected results for evaluating these expressions
+ */
+ private static final int[] RESULTS = { 97, 729, 133, 10, 5, 11, 3 };
+
+ /**
+ * @return A stream of objects, where each one is an expression and the
+ * expected result
+ * @since 2021-09-27
+ */
+ private static final Stream<Arguments> testParseExpressionData() {
+ return IntStream.range(0, TEST_EXPRESSIONS.size())
+ .mapToObj(i -> Arguments.of(TEST_EXPRESSIONS.get(i), RESULTS[i]));
+ }
+
+ /**
+ * Test method for
+ * {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}.
+ */
+ @ParameterizedTest
+ @MethodSource("testParseExpressionData")
+ public void testParseExpression(String expression, int value) {
+ assertEquals(value, numberParser.parseExpression(expression));
}
-
}
diff --git a/src/test/java/sevenUnits/utils/ObjectProductTest.java b/src/test/java/sevenUnits/utils/ObjectProductTest.java
index 13fd7ec..15ff277 100644
--- a/src/test/java/sevenUnits/utils/ObjectProductTest.java
+++ b/src/test/java/sevenUnits/utils/ObjectProductTest.java
@@ -30,7 +30,6 @@ import static sevenUnits.unit.Metric.Dimensions.VOLUME;
import org.junit.jupiter.api.Test;
import sevenUnits.unit.Metric;
-import sevenUnits.utils.ObjectProduct;
/**
* Tests for {@link ObjectProduct} using BaseDimension as a test object. This is NOT part of this program's public API.
diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
new file mode 100644
index 0000000..c891f20
--- /dev/null
+++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (C) 2021 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package sevenUnits.utils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static sevenUnits.utils.UncertainDouble.fromString;
+import static sevenUnits.utils.UncertainDouble.of;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for the UncertainDouble
+ *
+ * @author Adrien Hopkins
+ * @since 2021-11-29
+ */
+class UncertainDoubleTest {
+ @Test
+ final void testCompareTo() {
+ assertTrue(of(2.0, 0.5).compareTo(of(2.0, 0.1)) == 0);
+ assertTrue(of(2.0, 0.5).compareTo(of(1.0, 0.1)) > 0);
+ assertTrue(of(2.0, 0.5).compareTo(of(3.0, 0.1)) < 0);
+ }
+
+ @Test
+ final void testExactOperations() {
+ final UncertainDouble x = UncertainDouble.of(Math.PI, 0.1);
+
+ // slightly different because roundoff errors
+ final UncertainDouble x1 = UncertainDouble.of(Math.PI + Math.E - Math.E,
+ 0.1);
+ final UncertainDouble x2 = UncertainDouble.of(Math.PI * Math.E / Math.E,
+ 0.1);
+
+ // get results
+ final UncertainDouble result1 = x.plusExact(Math.E).minusExact(Math.E);
+ final UncertainDouble result2 = x.timesExact(Math.E)
+ .dividedByExact(Math.E);
+
+ // test that these operations work & don't change uncertainty
+ assertEquals(x1, result1);
+ assertTrue(x.equivalent(result1));
+ assertEquals(x2, result2);
+ assertTrue(x.equivalent(result2));
+
+ // exponents are different
+ assertEquals(Math.pow(Math.PI, Math.E),
+ x.toExponentExact(Math.E).value());
+ }
+
+ @Test
+ final void testFromString() {
+ // valid strings
+ assertEquals(of(2.0, 0.5), fromString("2.0 ± 0.5"));
+ assertEquals(of(2.0, 0.5), fromString("2.0 +- 0.5"));
+ assertEquals(of(2.0, 0.0), fromString("2.0"));
+
+ // invalid strings
+ for (final String s : List.of("2.A", "A", "2.0 ± .", "± 3.5")) {
+ assertThrows(IllegalArgumentException.class, () -> fromString(s));
+ }
+
+ // back and forth
+ assertEquals("2.0 ± 0.5", of(2.0, 0.5).toString());
+ assertEquals("2.0", of(2.0, 0).toString());
+ }
+
+ @Test
+ final void testHashCode() {
+ assertEquals(of(2.0, 0.5).hashCode(), fromString("2.0 ± 0.5").hashCode());
+ }
+}
diff --git a/src/test/resources/test-dimensionfile-invalid1.txt b/src/test/resources/test-dimensionfile-invalid1.txt
new file mode 100644
index 0000000..ff9ccd8
--- /dev/null
+++ b/src/test/resources/test-dimensionfile-invalid1.txt
@@ -0,0 +1,3 @@
+LENGTH
+MASS
+TIME
diff --git a/src/test/resources/test-dimensionfile-invalid2.txt b/src/test/resources/test-dimensionfile-invalid2.txt
new file mode 100644
index 0000000..2818cfc
--- /dev/null
+++ b/src/test/resources/test-dimensionfile-invalid2.txt
@@ -0,0 +1 @@
+NONEXISTENT !
diff --git a/src/test/resources/test-dimensionfile-invalid3.txt b/src/test/resources/test-dimensionfile-invalid3.txt
new file mode 100644
index 0000000..a16f941
--- /dev/null
+++ b/src/test/resources/test-dimensionfile-invalid3.txt
@@ -0,0 +1,5 @@
+LENGTH !
+MASS !
+TIME !
+
+BAD LENGTH +-+ TIME \ No newline at end of file
diff --git a/src/test/resources/test-dimensionfile-valid1.txt b/src/test/resources/test-dimensionfile-valid1.txt
new file mode 100644
index 0000000..fc6a426
--- /dev/null
+++ b/src/test/resources/test-dimensionfile-valid1.txt
@@ -0,0 +1,12 @@
+LENGTH !
+MASS !
+TIME !
+
+ENERGY MASS * LENGTH^2 / TIME^2
+POWER ENERGY / TIME
+
+# doesn't work, but would require major changes to fix properly
+# for now, just don't use brackets in dimension expressions
+# (note that the unit/prefix expressions use a complete hack
+# to enable this, one that doesn't work for dimensions)
+# POWER MASS * (LENGTH / TIME)^2 / TIME \ No newline at end of file
diff --git a/src/test/resources/test-unitsfile-invalid1.txt b/src/test/resources/test-unitsfile-invalid1.txt
new file mode 100644
index 0000000..813bf54
--- /dev/null
+++ b/src/test/resources/test-unitsfile-invalid1.txt
@@ -0,0 +1 @@
+justaunit \ No newline at end of file
diff --git a/src/test/resources/test-unitsfile-invalid2.txt b/src/test/resources/test-unitsfile-invalid2.txt
new file mode 100644
index 0000000..890ed13
--- /dev/null
+++ b/src/test/resources/test-unitsfile-invalid2.txt
@@ -0,0 +1 @@
+nonexistent !
diff --git a/src/test/resources/test-unitsfile-invalid3.txt b/src/test/resources/test-unitsfile-invalid3.txt
new file mode 100644
index 0000000..88d3c65
--- /dev/null
+++ b/src/test/resources/test-unitsfile-invalid3.txt
@@ -0,0 +1 @@
+badunit ^
diff --git a/src/test/resources/test-unitsfile-invalid4.txt b/src/test/resources/test-unitsfile-invalid4.txt
new file mode 100644
index 0000000..0fbc514
--- /dev/null
+++ b/src/test/resources/test-unitsfile-invalid4.txt
@@ -0,0 +1 @@
+badprefix- * 35 \ No newline at end of file
diff --git a/src/test/resources/test-unitsfile-invalid5.txt b/src/test/resources/test-unitsfile-invalid5.txt
new file mode 100644
index 0000000..c561393
--- /dev/null
+++ b/src/test/resources/test-unitsfile-invalid5.txt
@@ -0,0 +1 @@
+anotherinvalidunit (3 + 5) * (^) + 57 \ No newline at end of file
diff --git a/src/test/resources/test-unitsfile-valid1.txt b/src/test/resources/test-unitsfile-valid1.txt
new file mode 100644
index 0000000..1ea6cd9
--- /dev/null
+++ b/src/test/resources/test-unitsfile-valid1.txt
@@ -0,0 +1,6 @@
+U !
+V !
+
+test1 CBAU
+test2 BW CV
+test3 AU + CU - BU # easily the most complex one yet \ No newline at end of file
diff --git a/src/test/resources/test-unitsfile-valid2.txt b/src/test/resources/test-unitsfile-valid2.txt
new file mode 100644
index 0000000..7b15e27
--- /dev/null
+++ b/src/test/resources/test-unitsfile-valid2.txt
@@ -0,0 +1,3 @@
+A- 7
+B- 11
+C- 13 \ No newline at end of file