summaryrefslogtreecommitdiff
path: root/src/main/java/sevenUnits/unit/UnitDatabase.java
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2025-06-15 19:42:01 -0500
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2025-06-15 19:42:01 -0500
commit2fdbc084fd1d78f0b7633db34460b1195de264f3 (patch)
tree4c908950d9b049394f8160b8159b498aec586ecc /src/main/java/sevenUnits/unit/UnitDatabase.java
parented53492243ecad8d975401a97f5b634328ad2c71 (diff)
parentbccb5b5e3452421c81c1fb58f83391ba6584807c (diff)
Merge release 1.0.0 into stable branchHEADstable
See the tag 'v1.0.0' or the changelog for more information about this release.
Diffstat (limited to 'src/main/java/sevenUnits/unit/UnitDatabase.java')
-rw-r--r--src/main/java/sevenUnits/unit/UnitDatabase.java835
1 files changed, 419 insertions, 416 deletions
diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java
index 0120067..36c225f 100644
--- a/src/main/java/sevenUnits/unit/UnitDatabase.java
+++ b/src/main/java/sevenUnits/unit/UnitDatabase.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018-2024 Adrien Hopkins
+ * Copyright (C) 2018-2025 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
@@ -40,11 +40,9 @@ import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import sevenUnits.utils.ConditionalExistenceCollections;
-import sevenUnits.utils.DecimalComparison;
import sevenUnits.utils.ExpressionParser;
import sevenUnits.utils.NameSymbol;
import sevenUnits.utils.ObjectProduct;
@@ -52,7 +50,7 @@ import sevenUnits.utils.UncertainDouble;
/**
* A database of units, prefixes and dimensions, and their names.
- *
+ *
* @author Adrien Hopkins
* @since 2019-01-07
* @since v0.1.0
@@ -89,7 +87,7 @@ public final class UnitDatabase {
* Because of ambiguities between prefixes (i.e. kilokilo = mega),
* {@link #containsValue} and {@link #values()} currently ignore prefixes.
* </p>
- *
+ *
* @author Adrien Hopkins
* @since 2019-04-13
* @since v0.2.0
@@ -97,7 +95,7 @@ public final class UnitDatabase {
private static final class PrefixedUnitMap implements Map<String, Unit> {
/**
* The class used for entry sets.
- *
+ *
* <p>
* If the map that created this set is infinite in size (has at least one
* unit and at least one prefix), this set is infinite as well. If this
@@ -105,7 +103,7 @@ public final class UnitDatabase {
* {@code IllegalStateException} instead of creating an infinite-sized
* array.
* </p>
- *
+ *
* @author Adrien Hopkins
* @since 2019-04-13
* @since v0.2.0
@@ -114,7 +112,7 @@ public final class UnitDatabase {
extends AbstractSet<Map.Entry<String, Unit>> {
/**
* The entry for this set.
- *
+ *
* @author Adrien Hopkins
* @since 2019-04-14
* @since v0.2.0
@@ -126,7 +124,7 @@ public final class UnitDatabase {
/**
* Creates the {@code PrefixedUnitEntry}.
- *
+ *
* @param key key
* @param value value
* @since 2019-04-14
@@ -139,6 +137,7 @@ public final class UnitDatabase {
/**
* @since 2019-05-03
+ * @since v0.3.0
*/
@Override
public boolean equals(final Object o) {
@@ -161,6 +160,7 @@ public final class UnitDatabase {
/**
* @since 2019-05-03
+ * @since v0.3.0
*/
@Override
public int hashCode() {
@@ -180,8 +180,9 @@ public final class UnitDatabase {
* string is the string representation of the key, then the equals
* ({@code =}) character, then the string representation of the
* value.
- *
+ *
* @since 2019-05-03
+ * @since v0.3.0
*/
@Override
public String toString() {
@@ -192,7 +193,7 @@ public final class UnitDatabase {
/**
* An iterator that iterates over the units of a
* {@code PrefixedUnitNameSet}.
- *
+ *
* @author Adrien Hopkins
* @since 2019-04-14
* @since v0.2.0
@@ -212,7 +213,9 @@ public final class UnitDatabase {
/**
* Creates the
* {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
- *
+ *
+ * @param map map to base iterator on
+ *
* @since 2019-04-14
* @since v0.2.0
*/
@@ -228,7 +231,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private String getCurrentUnitName() {
- final StringBuilder unitName = new StringBuilder();
+ final var unitName = new StringBuilder();
for (final int i : this.prefixCoordinates) {
unitName.append(this.prefixNames.get(i));
}
@@ -241,18 +244,15 @@ public final class UnitDatabase {
public boolean hasNext() {
if (this.unitNames.isEmpty())
return false;
- else {
- if (this.prefixNames.isEmpty())
- return this.prefixCoordinates.isEmpty()
- && this.unitNamePosition < this.unitNames.size();
- else
- return true;
- }
+ if (this.prefixNames.isEmpty())
+ return this.prefixCoordinates.isEmpty()
+ && this.unitNamePosition < this.unitNames.size();
+ return true;
}
/**
* Changes this iterator's position to the next available one.
- *
+ *
* @since 2019-04-14
* @since v0.2.0
*/
@@ -268,7 +268,7 @@ public final class UnitDatabase {
this.prefixCoordinates.add(0, 0);
} else {
// get the prefix coordinate to increment, then increment
- int i = this.prefixCoordinates.size() - 1;
+ var i = this.prefixCoordinates.size() - 1;
this.prefixCoordinates.set(i,
this.prefixCoordinates.get(i) + 1);
@@ -294,7 +294,7 @@ public final class UnitDatabase {
@Override
public Entry<String, Unit> next() {
// get next element
- final Entry<String, Unit> nextEntry = this.peek();
+ final var nextEntry = this.peek();
// iterate to next position
this.incrementPosition();
@@ -306,6 +306,7 @@ public final class UnitDatabase {
* @return the next element in the iterator, without iterating over
* it
* @since 2019-05-03
+ * @since v0.3.0
*/
private Entry<String, Unit> peek() {
if (!this.hasNext())
@@ -322,7 +323,7 @@ public final class UnitDatabase {
}
}
- final String nextName = this.getCurrentUnitName();
+ final var nextName = this.getCurrentUnitName();
return new PrefixedUnitEntry(nextName, this.map.get(nextName));
}
@@ -330,8 +331,9 @@ public final class UnitDatabase {
/**
* Returns a string representation of the object. The exact details
* of the representation are unspecified and subject to change.
- *
+ *
* @since 2019-05-03
+ * @since v0.3.0
*/
@Override
public String toString() {
@@ -346,7 +348,7 @@ public final class UnitDatabase {
/**
* Creates the {@code PrefixedUnitNameSet}.
- *
+ *
* @param map map that created this set
* @since 2019-04-13
* @since v0.2.0
@@ -383,7 +385,7 @@ public final class UnitDatabase {
// This is OK because I'm in a try-catch block, catching the
// exact exception that would be thrown.
@SuppressWarnings("unchecked")
- final Entry<String, Unit> tempEntry = (Entry<String, Unit>) o;
+ final var tempEntry = (Entry<String, Unit>) o;
entry = tempEntry;
} catch (final ClassCastException e) {
throw new IllegalArgumentException(
@@ -441,55 +443,45 @@ public final class UnitDatabase {
public int size() {
if (this.map.units.isEmpty())
return 0;
- else {
- if (this.map.prefixes.isEmpty())
- return this.map.units.size();
- else
- // infinite set
- return Integer.MAX_VALUE;
- }
+ if (this.map.prefixes.isEmpty())
+ return this.map.units.size();
+ // infinite set
+ return Integer.MAX_VALUE;
}
- /**
- * @throws IllegalStateException if the set is infinite in size
- */
+ /** @throws IllegalStateException if the set is infinite in size */
@Override
public Object[] toArray() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray();
- else
- // infinite set
- throw new IllegalStateException(
- "Cannot make an infinite set into an array.");
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
}
- /**
- * @throws IllegalStateException if the set is infinite in size
- */
+ /** @throws IllegalStateException if the set is infinite in size */
@Override
public <T> T[] toArray(final T[] a) {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray(a);
- else
- // infinite set
- throw new IllegalStateException(
- "Cannot make an infinite set into an array.");
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
}
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toString();
- else
- return String.format(
- "Infinite set of name-unit entries created from units %s and prefixes %s",
- this.map.units, this.map.prefixes);
+ return String.format(
+ "Infinite set of name-unit entries created from units %s and prefixes %s",
+ this.map.units, this.map.prefixes);
}
}
/**
* The class used for unit name sets.
- *
+ *
* <p>
* If the map that created this set is infinite in size (has at least one
* unit and at least one prefix), this set is infinite as well. If this
@@ -497,7 +489,7 @@ public final class UnitDatabase {
* {@code IllegalStateException} instead of creating an infinite-sized
* array.
* </p>
- *
+ *
* @author Adrien Hopkins
* @since 2019-04-13
* @since v0.2.0
@@ -507,7 +499,7 @@ public final class UnitDatabase {
/**
* An iterator that iterates over the units of a
* {@code PrefixedUnitNameSet}.
- *
+ *
* @author Adrien Hopkins
* @since 2019-04-14
* @since v0.2.0
@@ -527,7 +519,9 @@ public final class UnitDatabase {
/**
* Creates the
* {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
- *
+ *
+ * @param map map to base itorator on
+ *
* @since 2019-04-14
* @since v0.2.0
*/
@@ -543,7 +537,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private String getCurrentUnitName() {
- final StringBuilder unitName = new StringBuilder();
+ final var unitName = new StringBuilder();
for (final int i : this.prefixCoordinates) {
unitName.append(this.prefixNames.get(i));
}
@@ -556,18 +550,15 @@ public final class UnitDatabase {
public boolean hasNext() {
if (this.unitNames.isEmpty())
return false;
- else {
- if (this.prefixNames.isEmpty())
- return this.prefixCoordinates.isEmpty()
- && this.unitNamePosition < this.unitNames.size();
- else
- return true;
- }
+ if (this.prefixNames.isEmpty())
+ return this.prefixCoordinates.isEmpty()
+ && this.unitNamePosition < this.unitNames.size();
+ return true;
}
/**
* Changes this iterator's position to the next available one.
- *
+ *
* @since 2019-04-14
* @since v0.2.0
*/
@@ -583,7 +574,7 @@ public final class UnitDatabase {
this.prefixCoordinates.add(0, 0);
} else {
// get the prefix coordinate to increment, then increment
- int i = this.prefixCoordinates.size() - 1;
+ var i = this.prefixCoordinates.size() - 1;
this.prefixCoordinates.set(i,
this.prefixCoordinates.get(i) + 1);
@@ -608,7 +599,7 @@ public final class UnitDatabase {
@Override
public String next() {
- final String nextName = this.peek();
+ final var nextName = this.peek();
this.incrementPosition();
@@ -619,6 +610,7 @@ public final class UnitDatabase {
* @return the next element in the iterator, without iterating over
* it
* @since 2019-05-03
+ * @since v0.3.0
*/
private String peek() {
if (!this.hasNext())
@@ -640,8 +632,9 @@ public final class UnitDatabase {
/**
* Returns a string representation of the object. The exact details
* of the representation are unspecified and subject to change.
- *
+ *
* @since 2019-05-03
+ * @since v0.3.0
*/
@Override
public String toString() {
@@ -656,7 +649,7 @@ public final class UnitDatabase {
/**
* Creates the {@code PrefixedUnitNameSet}.
- *
+ *
* @param map map that created this set
* @since 2019-04-13
* @since v0.2.0
@@ -734,56 +727,46 @@ public final class UnitDatabase {
public int size() {
if (this.map.units.isEmpty())
return 0;
- else {
- if (this.map.prefixes.isEmpty())
- return this.map.units.size();
- else
- // infinite set
- return Integer.MAX_VALUE;
- }
+ if (this.map.prefixes.isEmpty())
+ return this.map.units.size();
+ // infinite set
+ return Integer.MAX_VALUE;
}
- /**
- * @throws IllegalStateException if the set is infinite in size
- */
+ /** @throws IllegalStateException if the set is infinite in size */
@Override
public Object[] toArray() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray();
- else
- // infinite set
- throw new IllegalStateException(
- "Cannot make an infinite set into an array.");
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
}
- /**
- * @throws IllegalStateException if the set is infinite in size
- */
+ /** @throws IllegalStateException if the set is infinite in size */
@Override
public <T> T[] toArray(final T[] a) {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray(a);
- else
- // infinite set
- throw new IllegalStateException(
- "Cannot make an infinite set into an array.");
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
}
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toString();
- else
- return String.format(
- "Infinite set of name-unit entries created from units %s and prefixes %s",
- this.map.units, this.map.prefixes);
+ return String.format(
+ "Infinite set of name-unit entries created from units %s and prefixes %s",
+ this.map.units, this.map.prefixes);
}
}
/**
* The units stored in this collection, without prefixes.
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
@@ -791,7 +774,7 @@ public final class UnitDatabase {
/**
* The available prefixes for use.
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
@@ -804,7 +787,7 @@ public final class UnitDatabase {
/**
* Creates the {@code PrefixedUnitMap}.
- *
+ *
* @param units map mapping unit names to units
* @param prefixes map mapping prefix names to prefixes
* @since 2019-04-13
@@ -855,11 +838,11 @@ public final class UnitDatabase {
if (!(key instanceof String))
throw new IllegalArgumentException(
"Attempted to test for a unit using a non-string name.");
- final String unitName = (String) key;
+ final var unitName = (String) key;
// Then, look for the longest prefix that is attached to a valid unit
String longestPrefix = null;
- int longestLength = 0;
+ var longestLength = 0;
for (final String prefixName : this.prefixes.keySet()) {
// a prefix name is valid if:
@@ -871,7 +854,7 @@ public final class UnitDatabase {
// linear units can have prefixes)
if (unitName.startsWith(prefixName)
&& prefixName.length() > longestLength) {
- final String rest = unitName.substring(prefixName.length());
+ final var rest = unitName.substring(prefixName.length());
if (this.containsKey(rest)
&& this.get(rest) instanceof LinearUnit) {
longestPrefix = prefixName;
@@ -885,7 +868,7 @@ public final class UnitDatabase {
/**
* {@inheritDoc}
- *
+ *
* <p>
* Because of ambiguities between prefixes (i.e. kilokilo = mega), this
* method only tests for prefixless units.
@@ -914,11 +897,11 @@ public final class UnitDatabase {
if (!(key instanceof String))
throw new IllegalArgumentException(
"Attempted to obtain a unit using a non-string name.");
- final String unitName = (String) key;
+ final var unitName = (String) key;
// Then, look for the longest prefix that is attached to a valid unit
String longestPrefix = null;
- int longestLength = 0;
+ var longestLength = 0;
for (final String prefixName : this.prefixes.keySet()) {
// a prefix name is valid if:
@@ -930,7 +913,7 @@ public final class UnitDatabase {
// linear units can have prefixes)
if (unitName.startsWith(prefixName)
&& prefixName.length() > longestLength) {
- final String rest = unitName.substring(prefixName.length());
+ final var rest = unitName.substring(prefixName.length());
if (this.containsKey(rest)
&& this.get(rest) instanceof LinearUnit) {
longestPrefix = prefixName;
@@ -942,16 +925,14 @@ public final class UnitDatabase {
// if none found, returns null
if (longestPrefix == null)
return null;
- else {
- // get necessary data
- final String rest = unitName.substring(longestLength);
- // this cast will not fail because I verified that it would work
- // before selecting this prefix
- final LinearUnit unit = (LinearUnit) this.get(rest);
- final UnitPrefix prefix = this.prefixes.get(longestPrefix);
-
- return unit.withPrefix(prefix);
- }
+ // get necessary data
+ final var rest = unitName.substring(longestLength);
+ // this cast will not fail because I verified that it would work
+ // before selecting this prefix
+ final var unit = (LinearUnit) this.get(rest);
+ final var prefix = this.prefixes.get(longestPrefix);
+
+ return unit.withPrefix(prefix);
}
@Override
@@ -1028,28 +1009,24 @@ public final class UnitDatabase {
public int size() {
if (this.units.isEmpty())
return 0;
- else {
- if (this.prefixes.isEmpty())
- return this.units.size();
- else
- // infinite set
- return Integer.MAX_VALUE;
- }
+ if (this.prefixes.isEmpty())
+ return this.units.size();
+ // infinite set
+ return Integer.MAX_VALUE;
}
@Override
public String toString() {
if (this.units.isEmpty() || this.prefixes.isEmpty())
return new HashMap<>(this).toString();
- else
- return String.format(
- "Infinite map of name-unit entries created from units %s and prefixes %s",
- this.units, this.prefixes);
+ return String.format(
+ "Infinite map of name-unit entries created from units %s and prefixes %s",
+ this.units, this.prefixes);
}
/**
* {@inheritDoc}
- *
+ *
* <p>
* Because of ambiguities between prefixes (i.e. kilokilo = mega), this
* method ignores prefixes.
@@ -1066,34 +1043,6 @@ public final class UnitDatabase {
}
/**
- * Replacements done to *all* expression types
- */
- private static final Map<Pattern, String> EXPRESSION_REPLACEMENTS = new HashMap<>();
-
- // add data to expression replacements
- static {
- // add spaces around operators
- for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) {
- EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator),
- " " + operator + " ");
- }
-
- // replace multiple spaces with a single space
- EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " ");
- // place brackets around any expression of the form "number unit", with or
- // without the space
- EXPRESSION_REPLACEMENTS.put(Pattern.compile("((?:-?[1-9]\\d*|0)" // integer
- + "(?:\\.\\d+(?:[eE]\\d+))?)" // optional decimal point with numbers
- // after it
- + "\\s*" // optional space(s)
- + "([a-zA-Z]+(?:\\^\\d+)?" // any string of letters
- + "(?:\\s+[a-zA-Z]+(?:\\^\\d+)?))" // optional other letters
- + "(?!-?\\d)" // no number directly afterwards (avoids matching
- // "1e3")
- ), "\\($1 $2\\)");
- }
-
- /**
* A regular expression that separates names and expressions in unit files.
*/
private static final Pattern NAME_EXPRESSION = Pattern
@@ -1108,66 +1057,76 @@ public final class UnitDatabase {
/**
* The exponent operator
- *
+ *
* @param base base of exponentiation
* @param exponentUnit exponent
* @return result
* @since 2019-04-10
* @since v0.2.0
*/
- private static final LinearUnit exponentiateUnits(final LinearUnit base,
+ private static LinearUnit exponentiateUnits(final LinearUnit base,
final LinearUnit exponentUnit) {
- // exponent function - first check if o2 is a number,
- if (exponentUnit.getBase().equals(Metric.ONE.getBase())) {
- // then check if it is an integer,
- final double exponent = exponentUnit.getConversionFactor();
- if (DecimalComparison.equals(exponent % 1, 0))
- // then exponentiate
- return base.toExponent((int) (exponent + 0.5));
- else
- // not an integer
- throw new UnsupportedOperationException(
- "Decimal exponents are currently not supported.");
- } else
- // not a number
- throw new IllegalArgumentException("Exponents must be numbers.");
+ if (!exponentUnit.getBase().equals(Metric.ONE.getBase()))
+ throw new IllegalArgumentException(String.format(
+ "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.",
+ base, exponentUnit));
+
+ final var exponent = exponentUnit.getConversionFactor();
+ return base.toExponentRounded(exponent);
}
/**
* The exponent operator
- *
+ *
* @param base base of exponentiation
* @param exponentUnit exponent
* @return result
* @since 2020-08-04
+ * @since v0.3.0
*/
- private static final LinearUnitValue exponentiateUnitValues(
+ private static LinearUnitValue exponentiateUnitValues(
final LinearUnitValue base, final LinearUnitValue exponentValue) {
- // exponent function - first check if o2 is a number,
- if (exponentValue.canConvertTo(Metric.ONE)) {
- // then check if it is an integer,
- final double exponent = exponentValue.getValueExact();
- if (DecimalComparison.equals(exponent % 1, 0))
- // then exponentiate
- return base.toExponent((int) (exponent + 0.5));
- else
- // not an integer
- throw new UnsupportedOperationException(
- "Decimal exponents are currently not supported.");
- } else
- // not a number
- throw new IllegalArgumentException("Exponents must be numbers.");
+ if (!exponentValue.canConvertTo(Metric.ONE))
+ throw new IllegalArgumentException(String.format(
+ "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.",
+ base, exponentValue));
+
+ final var exponent = exponentValue.getValueExact();
+ return base.toExponentRounded(exponent);
+ }
+
+ /**
+ * Formats an expression so it can be parsed by the expression parser.
+ *
+ * Specifically, puts spaces around all operators so they can be parsed as
+ * words.
+ *
+ * @param expression expression to format
+ * @return formatted expression
+ * @since 2025-06-07
+ * @since v1.0.0
+ */
+ static String formatExpression(String expression) {
+ var modifiedExpression = expression;
+ for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) {
+ modifiedExpression = modifiedExpression.replaceAll(operator,
+ " " + operator + " ");
+ }
+
+ modifiedExpression = modifiedExpression.replaceAll("\\s+", " ");
+ return modifiedExpression;
}
/**
* @return true if entry represents a removable duplicate entry of map.
* @since 2021-05-22
+ * @since v0.3.0
*/
static <T> boolean isRemovableDuplicate(Map<String, T> map,
Entry<String, T> entry) {
for (final Entry<String, T> e : map.entrySet()) {
- final String name = e.getKey();
- final T value = e.getValue();
+ final var name = e.getKey();
+ final var value = e.getValue();
if (lengthFirstComparator.compare(entry.getKey(), name) < 0
&& Objects.equals(map.get(entry.getKey()), value))
return true;
@@ -1177,7 +1136,7 @@ public final class UnitDatabase {
/**
* The units in this system, excluding prefixes.
- *
+ *
* @since 2019-01-07
* @since v0.1.0
*/
@@ -1185,7 +1144,7 @@ public final class UnitDatabase {
/**
* The unit prefixes in this system.
- *
+ *
* @since 2019-01-14
* @since v0.1.0
*/
@@ -1193,7 +1152,7 @@ public final class UnitDatabase {
/**
* The dimensions in this system.
- *
+ *
* @since 2019-03-14
* @since v0.2.0
*/
@@ -1201,13 +1160,21 @@ public final class UnitDatabase {
/**
* A map mapping strings to units (including prefixes)
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
private final Map<String, Unit> units;
/**
+ * A map mapping strings to unit sets
+ *
+ * @since 2024-08-16
+ * @since v1.0.0
+ */
+ private final Map<String, List<LinearUnit>> unitSets;
+
+ /**
* The rule that specifies when prefix repetition is allowed. It takes in one
* argument: a list of the prefixes being applied to the unit
* <p>
@@ -1221,71 +1188,72 @@ public final class UnitDatabase {
/**
* A parser that can parse unit expressions.
- *
+ *
* @since 2019-03-22
* @since v0.2.0
*/
private final ExpressionParser<LinearUnit> unitExpressionParser = new ExpressionParser.Builder<>(
- this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
- .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
- .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
- .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3)
- .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2).build();
+ this::getLinearUnit).addBinaryOperator("+", LinearUnit::plus, 0)
+ .addBinaryOperator("-", LinearUnit::minus, 0)
+ .addBinaryOperator("*", LinearUnit::times, 1)
+ .addBinaryOperator("space_times", LinearUnit::times, 2)
+ .addSpaceFunction("space_times")
+ .addBinaryOperator("/", LinearUnit::dividedBy, 1)
+ .addBinaryOperator("|", LinearUnit::dividedBy, 4)
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 3).build();
/**
* A parser that can parse unit value expressions.
- *
+ *
* @since 2020-08-04
+ * @since v0.3.0
*/
private final ExpressionParser<LinearUnitValue> unitValueExpressionParser = new ExpressionParser.Builder<>(
this::getLinearUnitValue)
- .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
- .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
- .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
- .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3)
- .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2)
+ .addBinaryOperator("+", LinearUnitValue::plus, 0)
+ .addBinaryOperator("-", LinearUnitValue::minus, 0)
+ .addBinaryOperator("*", LinearUnitValue::times, 1)
+ .addBinaryOperator("space_times", LinearUnitValue::times, 2)
+ .addSpaceFunction("space_times")
+ .addBinaryOperator("/", LinearUnitValue::dividedBy, 1)
+ .addBinaryOperator("|", LinearUnitValue::dividedBy, 4)
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 3)
.build();
/**
* A parser that can parse unit prefix expressions
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
private final ExpressionParser<UnitPrefix> prefixExpressionParser = new ExpressionParser.Builder<>(
- this::getPrefix).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
- .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
- .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
- .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3)
- .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()),
- 2)
+ this::getPrefix).addBinaryOperator("+", UnitPrefix::plus, 0)
+ .addBinaryOperator("-", UnitPrefix::minus, 0)
+ .addBinaryOperator("*", UnitPrefix::times, 1).addSpaceFunction("*")
+ .addBinaryOperator("/", UnitPrefix::dividedBy, 1)
+ .addBinaryOperator("|", UnitPrefix::dividedBy, 3).addBinaryOperator(
+ "^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), 2)
.build();
/**
* A parser that can parse unit dimension expressions.
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
private final ExpressionParser<ObjectProduct<BaseDimension>> unitDimensionParser = new ExpressionParser.Builder<>(
- this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
+ this::getDimension).addBinaryOperator("*", ObjectProduct::times, 0)
.addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
- .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2)
+ .addBinaryOperator("/", ObjectProduct::dividedBy, 0)
+ .addBinaryOperator("|", ObjectProduct::dividedBy, 2)
.addNumericOperator("^", (o1, o2) -> {
- int exponent = (int) Math.round(o2.value());
+ final var exponent = (int) Math.round(o2.value());
return o1.toExponent(exponent);
}, 1).build();
/**
* Creates the {@code UnitsDatabase}.
- *
+ *
* @since 2019-01-10
* @since v0.1.0
*/
@@ -1295,10 +1263,11 @@ public final class UnitDatabase {
/**
* Creates the {@code UnitsDatabase}
- *
+ *
* @param prefixRepetitionRule the rule that determines when prefix
* repetition is allowed
* @since 2020-08-26
+ * @since v0.3.0
*/
public UnitDatabase(Predicate<List<UnitPrefix>> prefixRepetitionRule) {
this.prefixlessUnits = new HashMap<>();
@@ -1309,11 +1278,12 @@ public final class UnitDatabase {
new PrefixedUnitMap(this.prefixlessUnits, this.prefixes),
entry -> this.prefixRepetitionRule
.test(this.getPrefixesFromName(entry.getKey())));
+ this.unitSets = new HashMap<>();
}
/**
* Adds a unit dimension to the database.
- *
+ *
* @param name dimension's name
* @param dimension dimension to add
* @throws NullPointerException if name or dimension is null
@@ -1324,14 +1294,14 @@ public final class UnitDatabase {
final ObjectProduct<BaseDimension> dimension) {
Objects.requireNonNull(name, "name may not be null");
Objects.requireNonNull(dimension, "dimension may not be null");
- final ObjectProduct<BaseDimension> namedDimension = dimension
+ final var namedDimension = dimension
.withName(dimension.getNameSymbol().withExtraName(name));
this.dimensions.put(name, namedDimension);
}
/**
* Adds to the list from a line in a unit dimension file.
- *
+ *
* @param line line to look at
* @param lineCounter number of line, for error messages
* @since 2019-04-10
@@ -1349,13 +1319,13 @@ public final class UnitDatabase {
}
// divide line into name and expression
- final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
+ final var lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
throw new IllegalArgumentException(String.format(
"Error at line %d: Lines of a dimension file must consist of a dimension name, then spaces or tabs, then a dimension expression.",
lineCounter));
- final String name = lineMatcher.group(1);
- final String expression = lineMatcher.group(2);
+ final var name = lineMatcher.group(1);
+ final var expression = lineMatcher.group(2);
// if (name.endsWith(" ")) {
// System.err.printf("Warning - line %d's dimension name ends in a space",
@@ -1369,22 +1339,13 @@ public final class UnitDatabase {
throw new IllegalArgumentException(String.format(
"! used but no dimension found (line %d).", lineCounter));
} else {
- // it's a unit, get the unit
- final ObjectProduct<BaseDimension> dimension;
- try {
- dimension = this.getDimensionFromExpression(expression);
- } catch (final IllegalArgumentException | NoSuchElementException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
-
- this.addDimension(name, dimension);
+ this.addDimension(name, this.getDimensionFromExpression(expression));
}
}
/**
* Adds a unit prefix to the database.
- *
+ *
* @param name prefix's name
* @param prefix prefix to add
* @throws NullPointerException if name or prefix is null
@@ -1401,7 +1362,7 @@ public final class UnitDatabase {
/**
* Adds a unit to the database.
- *
+ *
* @param name unit's name
* @param unit unit to add
* @throws NullPointerException if unit is null
@@ -1418,7 +1379,7 @@ public final class UnitDatabase {
/**
* Adds to the list from a line in a unit file.
- *
+ *
* @param line line to look at
* @param lineCounter number of line, for error messages
* @since 2019-04-10
@@ -1436,14 +1397,14 @@ public final class UnitDatabase {
}
// divide line into name and expression
- final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
+ final var lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
throw new IllegalArgumentException(String.format(
"Error at line %d: Lines of a unit file must consist of a unit name, then spaces or tabs, then a unit expression.",
lineCounter));
- final String name = lineMatcher.group(1);
+ final var name = lineMatcher.group(1);
- final String expression = lineMatcher.group(2);
+ final var expression = lineMatcher.group(2);
// this code should never occur
// if (name.endsWith(" ")) {
@@ -1457,47 +1418,55 @@ public final class UnitDatabase {
if (!this.containsUnitName(name))
throw new IllegalArgumentException(String
.format("! used but no unit found (line %d).", lineCounter));
+ } else if (name.endsWith("-")) {
+ final var prefixName = name.substring(0, name.length() - 1);
+ this.addPrefix(prefixName, this.getPrefixFromExpression(expression));
+ } else if (expression.contains(";")) {
+ // it's a multi-unit
+ this.addUnitSet(name, this.getUnitSetFromExpression(expression));
} else {
- if (name.endsWith("-")) {
- final UnitPrefix prefix;
- try {
- prefix = this.getPrefixFromExpression(expression);
- } catch (final IllegalArgumentException
- | NoSuchElementException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
- final String prefixName = name.substring(0, name.length() - 1);
- this.addPrefix(prefixName, prefix);
- } else {
- // it's a unit, get the unit
- final Unit unit;
- try {
- unit = this.getUnitFromExpression(expression);
- } catch (final IllegalArgumentException
- | NoSuchElementException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
- this.addUnit(name, unit);
- }
+ // it's a unit, get the unit
+ this.addUnit(name, this.getUnitFromExpression(expression));
}
}
/**
- * Removes all units, prefixes and dimensions from this database.
- *
+ * Add a unit set to the database.
+ *
+ * @param name name of unit set
+ * @param value unit set to add
+ * @since 2024-08-16
+ * @since v1.0.0
+ */
+ public void addUnitSet(String name, List<LinearUnit> value) {
+ if (value.isEmpty())
+ throw new IllegalArgumentException("Unit sets must not be empty.");
+ for (final LinearUnit unit : value.subList(1, value.size())) {
+ if (!Objects.equals(unit.getDimension(), value.get(0).getDimension()))
+ throw new IllegalArgumentException(
+ "Unit sets must be all the same dimension, " + value
+ + " is not.");
+ }
+
+ this.unitSets.put(name, value);
+ }
+
+ /**
+ * Removes all units, unit sets, prefixes and dimensions from this database.
+ *
* @since 2022-02-26
+ * @since v0.4.0
*/
public void clear() {
this.dimensions.clear();
this.prefixes.clear();
this.prefixlessUnits.clear();
+ this.unitSets.clear();
}
/**
* Tests if the database has a unit dimension with this name.
- *
+ *
* @param name name to test
* @return if database contains name
* @since 2019-03-14
@@ -1509,7 +1478,7 @@ public final class UnitDatabase {
/**
* Tests if the database has a unit prefix with this name.
- *
+ *
* @param name name to test
* @return if database contains name
* @since 2019-01-13
@@ -1522,7 +1491,7 @@ public final class UnitDatabase {
/**
* Tests if the database has a unit with this name, taking prefixes into
* consideration
- *
+ *
* @param name name to test
* @return if database contains name
* @since 2019-01-13
@@ -1533,6 +1502,19 @@ public final class UnitDatabase {
}
/**
+ * Returns true iff there is a unit set with this name.
+ *
+ * @param name name to check for
+ * @return true iff there is a unit set with this name
+ *
+ * @since 2024-08-16
+ * @since v1.0.0
+ */
+ public boolean containsUnitSetName(String name) {
+ return this.unitSets.containsKey(name);
+ }
+
+ /**
* @return a map mapping dimension names to dimensions
* @since 2019-04-13
* @since v0.2.0
@@ -1544,10 +1526,11 @@ public final class UnitDatabase {
/**
* Evaluates a unit expression, following the same rules as
* {@link #getUnitFromExpression}.
- *
+ *
* @param expression expression to parse
* @return {@code LinearUnitValue} representing value of expression
* @since 2020-08-04
+ * @since v0.3.0
*/
public LinearUnitValue evaluateUnitExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
@@ -1556,37 +1539,13 @@ public final class UnitDatabase {
if (this.containsUnitName(expression))
return this.getLinearUnitValue(expression);
- // force operators to have spaces
- String modifiedExpression = expression;
- modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
- modifiedExpression = modifiedExpression.replaceAll("-", " - ");
-
- // format expression
- for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
- .entrySet()) {
- modifiedExpression = replacement.getKey().matcher(modifiedExpression)
- .replaceAll(replacement.getValue());
- }
-
- // the previous operation breaks negative numbers, fix them!
- // (i.e. -2 becomes - 2)
- // FIXME the previous operaton also breaks stuff like "1e-5"
- for (int i = 0; i < modifiedExpression.length(); i++) {
- if (modifiedExpression.charAt(i) == '-'
- && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^')
- .contains(modifiedExpression.charAt(i - 2)))) {
- // found a broken negative number
- modifiedExpression = modifiedExpression.substring(0, i + 1)
- + modifiedExpression.substring(i + 2);
- }
- }
-
- return this.unitValueExpressionParser.parseExpression(modifiedExpression);
+ return this.unitValueExpressionParser
+ .parseExpression(formatExpression(expression));
}
/**
* Gets a unit dimension from the database using its name.
- *
+ *
* @param name dimension's name
* @return dimension
* @since 2019-03-14
@@ -1594,12 +1553,11 @@ public final class UnitDatabase {
*/
public ObjectProduct<BaseDimension> getDimension(final String name) {
Objects.requireNonNull(name, "name must not be null.");
- final ObjectProduct<BaseDimension> dimension = this.dimensions.get(name);
+ final var dimension = this.dimensions.get(name);
if (dimension == null)
throw new NoSuchElementException(
"No dimension with name \"" + name + "\".");
- else
- return dimension;
+ return dimension;
}
/**
@@ -1614,8 +1572,9 @@ public final class UnitDatabase {
* multiplication)</li>
* <li>The operator '^' which exponentiates. Exponents must be integers.</li>
* </ul>
- *
+ *
* @param expression expression to parse
+ * @return parsed unit dimension
* @throws IllegalArgumentException if the expression cannot be parsed
* @throws NullPointerException if expression is null
* @since 2019-04-13
@@ -1629,23 +1588,14 @@ public final class UnitDatabase {
if (this.containsDimensionName(expression))
return this.getDimension(expression);
- // force operators to have spaces
- String modifiedExpression = expression;
-
- // format expression
- for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
- .entrySet()) {
- modifiedExpression = replacement.getKey().matcher(modifiedExpression)
- .replaceAll(replacement.getValue());
- }
-
- return this.unitDimensionParser.parseExpression(modifiedExpression);
+ return this.unitDimensionParser
+ .parseExpression(formatExpression(expression));
}
/**
* Gets a unit. If it is linear, cast it to a LinearUnit and return it.
* Otherwise, throw an {@code IllegalArgumentException}.
- *
+ *
* @param name unit's name
* @return unit
* @since 2019-03-22
@@ -1662,29 +1612,28 @@ public final class UnitDatabase {
"Format nonlinear units like: unit(value).");
// solve the function
- final Unit unit = this.getUnit(parts.get(0));
- final double value = Double.parseDouble(
+ final var unit = this.getUnit(parts.get(0));
+ final var value = Double.parseDouble(
parts.get(1).substring(0, parts.get(1).length() - 1));
return LinearUnit.fromUnitValue(unit, value);
- } else {
- // get a linear unit
- final Unit unit = this.getUnit(name);
-
- if (unit instanceof LinearUnit)
- return (LinearUnit) unit;
- else
- throw new IllegalArgumentException(
- String.format("%s is not a linear unit.", name));
}
+ // get a linear unit
+ final var unit = this.getUnit(name);
+
+ if (unit instanceof LinearUnit)
+ return (LinearUnit) unit;
+ throw new IllegalArgumentException(
+ String.format("%s is not a linear unit.", name));
}
/**
* Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be
* converted to their base units.
- *
+ *
* @param name name of unit
* @return {@code LinearUnitValue} instance
* @since 2020-08-04
+ * @since v0.3.0
*/
LinearUnitValue getLinearUnitValue(final String name) {
try {
@@ -1698,7 +1647,7 @@ public final class UnitDatabase {
/**
* Gets a unit prefix from the database from its name
- *
+ *
* @param name prefix's name
* @return prefix
* @since 2019-01-10
@@ -1708,30 +1657,30 @@ public final class UnitDatabase {
try {
return UnitPrefix.valueOf(Double.parseDouble(name));
} catch (final NumberFormatException e) {
- final UnitPrefix prefix = this.prefixes.get(name);
+ final var prefix = this.prefixes.get(name);
if (prefix == null)
throw new NoSuchElementException(
"No prefix with name \"" + name + "\".");
- else
- return prefix;
+ return prefix;
}
}
/**
* Gets all of the prefixes that are on a unit name, in application order.
- *
+ *
* @param unitName name of unit
* @return prefixes
* @since 2020-08-26
+ * @since v0.3.0
*/
List<UnitPrefix> getPrefixesFromName(final String unitName) {
final List<UnitPrefix> prefixes = new ArrayList<>();
- String name = unitName;
+ var name = unitName;
while (!this.prefixlessUnits.containsKey(name)) {
// find the longest prefix
String longestPrefixName = null;
- int longestLength = name.length();
+ var longestLength = name.length();
while (longestPrefixName == null) {
longestLength--;
@@ -1744,7 +1693,7 @@ public final class UnitDatabase {
}
// longest prefix found!
- final UnitPrefix prefix = this.getPrefix(longestPrefixName);
+ final var prefix = this.getPrefix(longestPrefixName);
prefixes.add(0, prefix);
name = name.substring(longestLength);
}
@@ -1757,7 +1706,7 @@ public final class UnitDatabase {
* Currently, prefix expressions are much simpler than unit expressions: They
* are either a number or the name of another prefix
* </p>
- *
+ *
* @param expression expression to input
* @return prefix
* @throws IllegalArgumentException if expression cannot be parsed
@@ -1772,30 +1721,22 @@ public final class UnitDatabase {
if (this.containsUnitName(expression))
return this.getPrefix(expression);
- // force operators to have spaces
- String modifiedExpression = expression;
-
- // format expression
- for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
- .entrySet()) {
- modifiedExpression = replacement.getKey().matcher(modifiedExpression)
- .replaceAll(replacement.getValue());
- }
-
- return this.prefixExpressionParser.parseExpression(modifiedExpression);
+ return this.prefixExpressionParser
+ .parseExpression(formatExpression(expression));
}
/**
* @return the prefixRepetitionRule
* @since 2020-08-26
+ * @since v0.3.0
*/
- public final Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
+ public Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
return this.prefixRepetitionRule;
}
/**
* Gets a unit from the database from its name, looking for prefixes.
- *
+ *
* @param name unit's name
* @return unit
* @since 2019-01-10
@@ -1803,27 +1744,28 @@ public final class UnitDatabase {
*/
public Unit getUnit(final String name) {
try {
- final double value = Double.parseDouble(name);
+ final var value = Double.parseDouble(name);
return Metric.ONE.times(value);
} catch (final NumberFormatException e) {
- final Unit unit = this.units.get(name);
+ final var unit = this.units.get(name);
if (unit == null)
throw new NoSuchElementException("No unit " + name);
- else if (unit.getPrimaryName().isEmpty())
+ if (unit.getPrimaryName().isEmpty())
return unit.withName(NameSymbol.ofName(name));
- else if (!unit.getPrimaryName().get().equals(name)) {
+ if (!unit.getPrimaryName().get().equals(name)) {
final Set<String> otherNames = new HashSet<>(unit.getOtherNames());
otherNames.add(unit.getPrimaryName().get());
return unit.withName(NameSymbol.ofNullable(name,
unit.getSymbol().orElse(null), otherNames));
- } else if (!unit.getOtherNames().contains(name)) {
+ }
+ if (!unit.getOtherNames().contains(name)) {
final Set<String> otherNames = new HashSet<>(unit.getOtherNames());
otherNames.add(name);
return unit.withName(
NameSymbol.ofNullable(unit.getPrimaryName().orElse(null),
unit.getSymbol().orElse(null), otherNames));
- } else
- return unit;
+ }
+ return unit;
}
}
@@ -1842,8 +1784,9 @@ public final class UnitDatabase {
* <li>A number which is multiplied or divided</li>
* </ul>
* This method only works with linear units.
- *
+ *
* @param expression expression to parse
+ * @return parsed unit
* @throws IllegalArgumentException if the expression cannot be parsed
* @throws NullPointerException if expression is null
* @since 2019-01-07
@@ -1856,31 +1799,51 @@ public final class UnitDatabase {
if (this.containsUnitName(expression))
return this.getUnit(expression);
- // force operators to have spaces
- String modifiedExpression = expression;
- modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
- modifiedExpression = modifiedExpression.replaceAll("-", " - ");
+ return this.unitExpressionParser
+ .parseExpression(formatExpression(expression));
+ }
- // format expression
- for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
- .entrySet()) {
- modifiedExpression = replacement.getKey().matcher(modifiedExpression)
- .replaceAll(replacement.getValue());
- }
+ /**
+ * Get a unit set from its name, throwing a {@link NoSuchElementException} if
+ * there is none.
+ *
+ * @param name name of unit set
+ * @return unit set with that name
+ *
+ * @since 2024-08-16
+ * @since v1.0.0
+ */
+ public List<LinearUnit> getUnitSet(String name) {
+ final var unitSet = this.unitSets.get(name);
+ if (unitSet == null)
+ throw new NoSuchElementException("No unit set with name " + name);
+ return unitSet;
+ }
- // the previous operation breaks negative numbers, fix them!
- // (i.e. -2 becomes - 2)
- for (int i = 0; i < modifiedExpression.length(); i++) {
- if (modifiedExpression.charAt(i) == '-'
- && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^')
- .contains(modifiedExpression.charAt(i - 2)))) {
- // found a broken negative number
- modifiedExpression = modifiedExpression.substring(0, i + 1)
- + modifiedExpression.substring(i + 2);
- }
- }
+ /**
+ * Parses a semicolon-separated expression to get the unit set being used.
+ *
+ * @since 2024-08-22
+ * @since v1.0.0
+ */
+ List<LinearUnit> getUnitSetFromExpression(String expression) {
+ final var parts = expression.split(";");
+ final List<LinearUnit> units = new ArrayList<>(parts.length);
+ for (final String unitName : parts) {
+ final var unit = this.getUnitFromExpression(unitName.trim());
- return this.unitExpressionParser.parseExpression(modifiedExpression);
+ if (!(unit instanceof LinearUnit))
+ throw new IllegalArgumentException(String.format(
+ "Unit '%s' is in a unit-set expression, but is not linear.",
+ unitName));
+ if (units.size() > 0 && !unit.canConvertTo(units.get(0)))
+ throw new IllegalArgumentException(String.format(
+ "Units in expression '%s' have different dimensions.",
+ expression));
+
+ units.add((LinearUnit) unit);
+ }
+ return units;
}
/**
@@ -1901,26 +1864,32 @@ public final class UnitDatabase {
* no unit is found, an IllegalArgumentException is thrown. This is used to
* define initial units and ensure that the database contains them.</li>
* </ul>
- *
+ *
* @param file file to read
- * @throws IllegalArgumentException if the file cannot be parsed, found or
- * read
- * @throws NullPointerException if file is null
+ * @throws NullPointerException if file is null
+ * @return list of errors that happened when loading file
* @since 2019-01-13
* @since v0.1.0
*/
- public void loadDimensionFile(final Path file) {
+ public List<LoadingException> loadDimensionFile(final Path file) {
Objects.requireNonNull(file, "file must not be null.");
+ final List<LoadingException> errors = new ArrayList<>();
try {
- long lineCounter = 0;
+ var lineCounter = 0L;
for (final String line : Files.readAllLines(file)) {
- this.addDimensionFromLine(line, ++lineCounter);
+ try {
+ this.addDimensionFromLine(line, ++lineCounter);
+ } catch (IllegalArgumentException | NoSuchElementException e) {
+ errors.add(new LoadingException(lineCounter, line, file,
+ LoadingException.FileType.DIMENSION, e));
+ }
}
} catch (final FileNotFoundException e) {
throw new IllegalArgumentException("Could not find file " + file, e);
} catch (final IOException e) {
throw new IllegalArgumentException("Could not read file " + file, e);
}
+ return errors;
}
/**
@@ -1928,15 +1897,26 @@ public final class UnitDatabase {
* {@link #loadDimensionFile}.
*
* @param stream stream to load from
+ * @return list of all errors that happened loading the stream
* @since 2021-03-27
+ * @since v0.3.0
*/
- public void loadDimensionsFromStream(final InputStream stream) {
- try (final Scanner scanner = new Scanner(stream)) {
- long lineCounter = 0;
+ public List<LoadingException> loadDimensionsFromStream(
+ final InputStream stream) {
+ final List<LoadingException> errors = new ArrayList<>();
+ try (final var scanner = new Scanner(stream)) {
+ var lineCounter = 0L;
while (scanner.hasNextLine()) {
- this.addDimensionFromLine(scanner.nextLine(), ++lineCounter);
+ final var line = scanner.nextLine();
+ try {
+ this.addDimensionFromLine(line, ++lineCounter);
+ } catch (IllegalArgumentException | NoSuchElementException e) {
+ errors.add(new LoadingException(lineCounter, line,
+ LoadingException.FileType.DIMENSION, e));
+ }
}
}
+ return errors;
}
/**
@@ -1956,26 +1936,32 @@ public final class UnitDatabase {
* no unit is found, an IllegalArgumentException is thrown. This is used to
* define initial units and ensure that the database contains them.</li>
* </ul>
- *
+ *
* @param file file to read
- * @throws IllegalArgumentException if the file cannot be parsed, found or
- * read
- * @throws NullPointerException if file is null
+ * @throws NullPointerException if file is null
+ * @return list of errors that happened when loading file
* @since 2019-01-13
* @since v0.1.0
*/
- public void loadUnitsFile(final Path file) {
+ public List<LoadingException> loadUnitsFile(final Path file) {
Objects.requireNonNull(file, "file must not be null.");
+ final List<LoadingException> errors = new ArrayList<>();
try {
- long lineCounter = 0;
+ var lineCounter = 0L;
for (final String line : Files.readAllLines(file)) {
- this.addUnitOrPrefixFromLine(line, ++lineCounter);
+ try {
+ this.addUnitOrPrefixFromLine(line, ++lineCounter);
+ } catch (IllegalArgumentException | NoSuchElementException e) {
+ errors.add(new LoadingException(lineCounter, line, file,
+ LoadingException.FileType.UNIT, e));
+ }
}
} catch (final FileNotFoundException e) {
throw new IllegalArgumentException("Could not find file " + file, e);
} catch (final IOException e) {
throw new IllegalArgumentException("Could not read file " + file, e);
}
+ return errors;
}
/**
@@ -1983,15 +1969,25 @@ public final class UnitDatabase {
* {@link #loadUnitsFile}.
*
* @param stream stream to load from
+ * @return list of all errors that happened loading the stream
* @since 2021-03-27
+ * @since v0.3.0
*/
- public void loadUnitsFromStream(InputStream stream) {
- try (final Scanner scanner = new Scanner(stream)) {
- long lineCounter = 0;
+ public List<LoadingException> loadUnitsFromStream(InputStream stream) {
+ final List<LoadingException> errors = new ArrayList<>();
+ try (final var scanner = new Scanner(stream)) {
+ var lineCounter = 0L;
while (scanner.hasNextLine()) {
- this.addUnitOrPrefixFromLine(scanner.nextLine(), ++lineCounter);
+ final var line = scanner.nextLine();
+ try {
+ this.addUnitOrPrefixFromLine(line, ++lineCounter);
+ } catch (IllegalArgumentException | NoSuchElementException e) {
+ errors.add(new LoadingException(lineCounter, line,
+ LoadingException.FileType.UNIT, e));
+ }
}
}
+ return errors;
}
/**
@@ -2003,17 +1999,17 @@ public final class UnitDatabase {
public Map<String, UnitPrefix> prefixMap(boolean includeDuplicates) {
if (includeDuplicates)
return Collections.unmodifiableMap(this.prefixes);
- else
- return Collections.unmodifiableMap(ConditionalExistenceCollections
- .conditionalExistenceMap(this.prefixes,
- entry -> !isRemovableDuplicate(this.prefixes, entry)));
+ return Collections.unmodifiableMap(ConditionalExistenceCollections
+ .conditionalExistenceMap(this.prefixes,
+ entry -> !isRemovableDuplicate(this.prefixes, entry)));
}
/**
* @param prefixRepetitionRule the prefixRepetitionRule to set
* @since 2020-08-26
+ * @since v0.3.0
*/
- public final void setPrefixRepetitionRule(
+ public void setPrefixRepetitionRule(
Predicate<List<UnitPrefix>> prefixRepetitionRule) {
this.prefixRepetitionRule = prefixRepetitionRule;
}
@@ -2041,18 +2037,18 @@ public final class UnitDatabase {
* <p>
* Specifically, the operations that will throw an IllegalStateException if
* the map is infinite in size are:
+ * </p>
* <ul>
* <li>{@code unitMap.entrySet().toArray()} (either overloading)</li>
* <li>{@code unitMap.keySet().toArray()} (either overloading)</li>
* </ul>
- * </p>
* <p>
* Because of ambiguities between prefixes (i.e. kilokilo = mega), the map's
* {@link PrefixedUnitMap#containsValue containsValue} and
* {@link PrefixedUnitMap#values() values()} methods currently ignore
* prefixes.
* </p>
- *
+ *
* @return a map mapping unit names to units, including prefixed names
* @since 2019-04-13
* @since v0.2.0
@@ -2073,10 +2069,17 @@ public final class UnitDatabase {
public Map<String, Unit> unitMapPrefixless(boolean includeDuplicates) {
if (includeDuplicates)
return Collections.unmodifiableMap(this.prefixlessUnits);
- else
- return Collections.unmodifiableMap(ConditionalExistenceCollections
- .conditionalExistenceMap(this.prefixlessUnits,
- entry -> !isRemovableDuplicate(this.prefixlessUnits,
- entry)));
+ return Collections.unmodifiableMap(ConditionalExistenceCollections
+ .conditionalExistenceMap(this.prefixlessUnits,
+ entry -> !isRemovableDuplicate(this.prefixlessUnits, entry)));
+ }
+
+ /**
+ * @return an unmodifiable map mapping names to unit sets
+ * @since 2024-08-16
+ * @since v1.0.0
+ */
+ public Map<String, List<LinearUnit>> unitSetMap() {
+ return Collections.unmodifiableMap(this.unitSets);
}
}