summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/sevenUnits/unit/UnitDatabase.java527
-rw-r--r--src/main/java/sevenUnitsGUI/Presenter.java122
-rw-r--r--src/main/resources/metric_exceptions.txt1
-rw-r--r--src/main/resources/unitsfile.txt3
4 files changed, 391 insertions, 262 deletions
diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java
index 0120067..58c5cac 100644
--- a/src/main/java/sevenUnits/unit/UnitDatabase.java
+++ b/src/main/java/sevenUnits/unit/UnitDatabase.java
@@ -123,7 +123,7 @@ public final class UnitDatabase {
implements Entry<String, Unit> {
private final String key;
private final Unit value;
-
+
/**
* Creates the {@code PrefixedUnitEntry}.
*
@@ -136,7 +136,7 @@ public final class UnitDatabase {
this.key = key;
this.value = value;
}
-
+
/**
* @since 2019-05-03
*/
@@ -148,17 +148,17 @@ public final class UnitDatabase {
return Objects.equals(this.getKey(), other.getKey())
&& Objects.equals(this.getValue(), other.getValue());
}
-
+
@Override
public String getKey() {
return this.key;
}
-
+
@Override
public Unit getValue() {
return this.value;
}
-
+
/**
* @since 2019-05-03
*/
@@ -168,13 +168,13 @@ public final class UnitDatabase {
^ (this.getValue() == null ? 0
: this.getValue().hashCode());
}
-
+
@Override
public Unit setValue(final Unit value) {
throw new UnsupportedOperationException(
"Cannot set value in an immutable entry");
}
-
+
/**
* Returns a string representation of the entry. The format of the
* string is the string representation of the key, then the equals
@@ -188,7 +188,7 @@ public final class UnitDatabase {
return this.getKey() + "=" + this.getValue();
}
}
-
+
/**
* An iterator that iterates over the units of a
* {@code PrefixedUnitNameSet}.
@@ -203,12 +203,12 @@ public final class UnitDatabase {
private int unitNamePosition = 0;
// the indices of the prefixes attached to the current unit
private final List<Integer> prefixCoordinates = new ArrayList<>();
-
+
// values from the unit entry set
private final Map<String, Unit> map;
private transient final List<String> unitNames;
private transient final List<String> prefixNames;
-
+
/**
* Creates the
* {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
@@ -221,7 +221,7 @@ public final class UnitDatabase {
this.unitNames = new ArrayList<>(map.units.keySet());
this.prefixNames = new ArrayList<>(map.prefixes.keySet());
}
-
+
/**
* @return current unit name
* @since 2019-04-14
@@ -233,10 +233,10 @@ public final class UnitDatabase {
unitName.append(this.prefixNames.get(i));
}
unitName.append(this.unitNames.get(this.unitNamePosition));
-
+
return unitName.toString();
}
-
+
@Override
public boolean hasNext() {
if (this.unitNames.isEmpty())
@@ -249,7 +249,7 @@ public final class UnitDatabase {
return true;
}
}
-
+
/**
* Changes this iterator's position to the next available one.
*
@@ -258,11 +258,11 @@ public final class UnitDatabase {
*/
private void incrementPosition() {
this.unitNamePosition++;
-
+
if (this.unitNamePosition >= this.unitNames.size()) {
// we have used all of our units, go to a different prefix
this.unitNamePosition = 0;
-
+
// if the prefix coordinates are empty, then set it to [0]
if (this.prefixCoordinates.isEmpty()) {
this.prefixCoordinates.add(0, 0);
@@ -271,7 +271,7 @@ public final class UnitDatabase {
int i = this.prefixCoordinates.size() - 1;
this.prefixCoordinates.set(i,
this.prefixCoordinates.get(i) + 1);
-
+
// fix any carrying errors
while (i >= 0 && this.prefixCoordinates
.get(i) >= this.prefixNames.size()) {
@@ -279,7 +279,7 @@ public final class UnitDatabase {
this.prefixCoordinates.set(i--, 0); // null and
// decrement at the
// same time
-
+
if (i < 0) { // we need to add a new coordinate
this.prefixCoordinates.add(0, 0);
} else { // increment an existing one
@@ -290,18 +290,18 @@ public final class UnitDatabase {
}
}
}
-
+
@Override
public Entry<String, Unit> next() {
// get next element
final Entry<String, Unit> nextEntry = this.peek();
-
+
// iterate to next position
this.incrementPosition();
-
+
return nextEntry;
}
-
+
/**
* @return the next element in the iterator, without iterating over
* it
@@ -310,7 +310,7 @@ public final class UnitDatabase {
private Entry<String, Unit> peek() {
if (!this.hasNext())
throw new NoSuchElementException("No units left!");
-
+
// if I have prefixes, ensure I'm not using a nonlinear unit
// since all of the unprefixed stuff is done, just remove
// nonlinear units
@@ -321,12 +321,12 @@ public final class UnitDatabase {
this.unitNames.remove(this.unitNamePosition);
}
}
-
+
final String nextName = this.getCurrentUnitName();
-
+
return new PrefixedUnitEntry(nextName, this.map.get(nextName));
}
-
+
/**
* Returns a string representation of the object. The exact details
* of the representation are unspecified and subject to change.
@@ -340,10 +340,10 @@ public final class UnitDatabase {
this.peek());
}
}
-
+
// the map that created this set
private final PrefixedUnitMap map;
-
+
/**
* Creates the {@code PrefixedUnitNameSet}.
*
@@ -354,31 +354,31 @@ public final class UnitDatabase {
public PrefixedUnitEntrySet(final PrefixedUnitMap map) {
this.map = map;
}
-
+
@Override
public boolean add(final Map.Entry<String, Unit> e) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public boolean addAll(
final Collection<? extends Map.Entry<String, Unit>> c) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public void clear() {
throw new UnsupportedOperationException(
"Cannot clear an immutable set");
}
-
+
@Override
public boolean contains(final Object o) {
// get the entry
final Entry<String, Unit> entry;
-
+
try {
// This is OK because I'm in a try-catch block, catching the
// exact exception that would be thrown.
@@ -389,11 +389,11 @@ public final class UnitDatabase {
throw new IllegalArgumentException(
"Attempted to test for an entry using a non-entry.");
}
-
+
return this.map.containsKey(entry.getKey())
&& this.map.get(entry.getKey()).equals(entry.getValue());
}
-
+
@Override
public boolean containsAll(final Collection<?> c) {
for (final Object o : c)
@@ -401,42 +401,42 @@ public final class UnitDatabase {
return false;
return true;
}
-
+
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
-
+
@Override
public Iterator<Entry<String, Unit>> iterator() {
return new PrefixedUnitEntryIterator(this.map);
}
-
+
@Override
public boolean remove(final Object o) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeIf(
final Predicate<? super Entry<String, Unit>> filter) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean retainAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public int size() {
if (this.map.units.isEmpty())
@@ -449,7 +449,7 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -462,7 +462,7 @@ public final class UnitDatabase {
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -475,7 +475,7 @@ public final class UnitDatabase {
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
}
-
+
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
@@ -486,7 +486,7 @@ public final class UnitDatabase {
this.map.units, this.map.prefixes);
}
}
-
+
/**
* The class used for unit name sets.
*
@@ -518,12 +518,12 @@ public final class UnitDatabase {
private int unitNamePosition = 0;
// the indices of the prefixes attached to the current unit
private final List<Integer> prefixCoordinates = new ArrayList<>();
-
+
// values from the unit name set
private final Map<String, Unit> map;
private transient final List<String> unitNames;
private transient final List<String> prefixNames;
-
+
/**
* Creates the
* {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
@@ -536,7 +536,7 @@ public final class UnitDatabase {
this.unitNames = new ArrayList<>(map.units.keySet());
this.prefixNames = new ArrayList<>(map.prefixes.keySet());
}
-
+
/**
* @return current unit name
* @since 2019-04-14
@@ -548,10 +548,10 @@ public final class UnitDatabase {
unitName.append(this.prefixNames.get(i));
}
unitName.append(this.unitNames.get(this.unitNamePosition));
-
+
return unitName.toString();
}
-
+
@Override
public boolean hasNext() {
if (this.unitNames.isEmpty())
@@ -564,7 +564,7 @@ public final class UnitDatabase {
return true;
}
}
-
+
/**
* Changes this iterator's position to the next available one.
*
@@ -573,11 +573,11 @@ public final class UnitDatabase {
*/
private void incrementPosition() {
this.unitNamePosition++;
-
+
if (this.unitNamePosition >= this.unitNames.size()) {
// we have used all of our units, go to a different prefix
this.unitNamePosition = 0;
-
+
// if the prefix coordinates are empty, then set it to [0]
if (this.prefixCoordinates.isEmpty()) {
this.prefixCoordinates.add(0, 0);
@@ -586,7 +586,7 @@ public final class UnitDatabase {
int i = this.prefixCoordinates.size() - 1;
this.prefixCoordinates.set(i,
this.prefixCoordinates.get(i) + 1);
-
+
// fix any carrying errors
while (i >= 0 && this.prefixCoordinates
.get(i) >= this.prefixNames.size()) {
@@ -594,7 +594,7 @@ public final class UnitDatabase {
this.prefixCoordinates.set(i--, 0); // null and
// decrement at the
// same time
-
+
if (i < 0) { // we need to add a new coordinate
this.prefixCoordinates.add(0, 0);
} else { // increment an existing one
@@ -605,16 +605,16 @@ public final class UnitDatabase {
}
}
}
-
+
@Override
public String next() {
final String nextName = this.peek();
-
+
this.incrementPosition();
-
+
return nextName;
}
-
+
/**
* @return the next element in the iterator, without iterating over
* it
@@ -633,10 +633,10 @@ public final class UnitDatabase {
this.unitNames.remove(this.unitNamePosition);
}
}
-
+
return this.getCurrentUnitName();
}
-
+
/**
* Returns a string representation of the object. The exact details
* of the representation are unspecified and subject to change.
@@ -650,10 +650,10 @@ public final class UnitDatabase {
this.peek());
}
}
-
+
// the map that created this set
private final PrefixedUnitMap map;
-
+
/**
* Creates the {@code PrefixedUnitNameSet}.
*
@@ -664,30 +664,30 @@ public final class UnitDatabase {
public PrefixedUnitNameSet(final PrefixedUnitMap map) {
this.map = map;
}
-
+
@Override
public boolean add(final String e) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public boolean addAll(final Collection<? extends String> c) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public void clear() {
throw new UnsupportedOperationException(
"Cannot clear an immutable set");
}
-
+
@Override
public boolean contains(final Object o) {
return this.map.containsKey(o);
}
-
+
@Override
public boolean containsAll(final Collection<?> c) {
for (final Object o : c)
@@ -695,41 +695,41 @@ public final class UnitDatabase {
return false;
return true;
}
-
+
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
-
+
@Override
public Iterator<String> iterator() {
return new PrefixedUnitNameIterator(this.map);
}
-
+
@Override
public boolean remove(final Object o) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeIf(final Predicate<? super String> filter) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean retainAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public int size() {
if (this.map.units.isEmpty())
@@ -742,7 +742,7 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -754,9 +754,9 @@ public final class UnitDatabase {
// infinite set
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
-
+
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -769,7 +769,7 @@ public final class UnitDatabase {
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
}
-
+
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
@@ -780,7 +780,7 @@ public final class UnitDatabase {
this.map.units, this.map.prefixes);
}
}
-
+
/**
* The units stored in this collection, without prefixes.
*
@@ -788,7 +788,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, Unit> units;
-
+
/**
* The available prefixes for use.
*
@@ -796,12 +796,12 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, UnitPrefix> prefixes;
-
+
// caches
private transient Collection<Unit> values = null;
private transient Set<String> keySet = null;
private transient Set<Entry<String, Unit>> entrySet = null;
-
+
/**
* Creates the {@code PrefixedUnitMap}.
*
@@ -817,50 +817,50 @@ public final class UnitDatabase {
this.units = Collections.unmodifiableMap(units);
this.prefixes = Collections.unmodifiableMap(prefixes);
}
-
+
@Override
public void clear() {
throw new UnsupportedOperationException(
"Cannot clear an immutable map");
}
-
+
@Override
public Unit compute(final String key,
final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
throw new UnsupportedOperationException(
"Cannot edit an immutable map");
}
-
+
@Override
public Unit computeIfAbsent(final String key,
final Function<? super String, ? extends Unit> mappingFunction) {
throw new UnsupportedOperationException(
"Cannot edit an immutable map");
}
-
+
@Override
public Unit computeIfPresent(final String key,
final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
throw new UnsupportedOperationException(
"Cannot edit an immutable map");
}
-
+
@Override
public boolean containsKey(final Object key) {
// First, test if there is a unit with the key
if (this.units.containsKey(key))
return true;
-
+
// Next, try to cast it to String
if (!(key instanceof String))
throw new IllegalArgumentException(
"Attempted to test for a unit using a non-string name.");
final String unitName = (String) key;
-
+
// Then, look for the longest prefix that is attached to a valid unit
String longestPrefix = null;
int longestLength = 0;
-
+
for (final String prefixName : this.prefixes.keySet()) {
// a prefix name is valid if:
// - it is prefixed (i.e. the unit name starts with it)
@@ -879,10 +879,10 @@ public final class UnitDatabase {
}
}
}
-
+
return longestPrefix != null;
}
-
+
/**
* {@inheritDoc}
*
@@ -895,7 +895,7 @@ public final class UnitDatabase {
public boolean containsValue(final Object value) {
return this.units.containsValue(value);
}
-
+
@Override
public Set<Entry<String, Unit>> entrySet() {
if (this.entrySet == null) {
@@ -903,23 +903,23 @@ public final class UnitDatabase {
}
return this.entrySet;
}
-
+
@Override
public Unit get(final Object key) {
// First, test if there is a unit with the key
if (this.units.containsKey(key))
return this.units.get(key);
-
+
// Next, try to cast it to String
if (!(key instanceof String))
throw new IllegalArgumentException(
"Attempted to obtain a unit using a non-string name.");
final String unitName = (String) key;
-
+
// Then, look for the longest prefix that is attached to a valid unit
String longestPrefix = null;
int longestLength = 0;
-
+
for (final String prefixName : this.prefixes.keySet()) {
// a prefix name is valid if:
// - it is prefixed (i.e. the unit name starts with it)
@@ -938,7 +938,7 @@ public final class UnitDatabase {
}
}
}
-
+
// if none found, returns null
if (longestPrefix == null)
return null;
@@ -949,16 +949,16 @@ public final class UnitDatabase {
// before selecting this prefix
final LinearUnit unit = (LinearUnit) this.get(rest);
final UnitPrefix prefix = this.prefixes.get(longestPrefix);
-
+
return unit.withPrefix(prefix);
}
}
-
+
@Override
public boolean isEmpty() {
return this.units.isEmpty();
}
-
+
@Override
public Set<String> keySet() {
if (this.keySet == null) {
@@ -966,64 +966,64 @@ public final class UnitDatabase {
}
return this.keySet;
}
-
+
@Override
public Unit merge(final String key, final Unit value,
final BiFunction<? super Unit, ? super Unit, ? extends Unit> remappingFunction) {
throw new UnsupportedOperationException(
"Cannot merge into an immutable map");
}
-
+
@Override
public Unit put(final String key, final Unit value) {
throw new UnsupportedOperationException(
"Cannot add entries to an immutable map");
}
-
+
@Override
public void putAll(final Map<? extends String, ? extends Unit> m) {
throw new UnsupportedOperationException(
"Cannot add entries to an immutable map");
}
-
+
@Override
public Unit putIfAbsent(final String key, final Unit value) {
throw new UnsupportedOperationException(
"Cannot add entries to an immutable map");
}
-
+
@Override
public Unit remove(final Object key) {
throw new UnsupportedOperationException(
"Cannot remove entries from an immutable map");
}
-
+
@Override
public boolean remove(final Object key, final Object value) {
throw new UnsupportedOperationException(
"Cannot remove entries from an immutable map");
}
-
+
@Override
public Unit replace(final String key, final Unit value) {
throw new UnsupportedOperationException(
"Cannot replace entries in an immutable map");
}
-
+
@Override
public boolean replace(final String key, final Unit oldValue,
final Unit newValue) {
throw new UnsupportedOperationException(
"Cannot replace entries in an immutable map");
}
-
+
@Override
public void replaceAll(
final BiFunction<? super String, ? super Unit, ? extends Unit> function) {
throw new UnsupportedOperationException(
"Cannot replace entries in an immutable map");
}
-
+
@Override
public int size() {
if (this.units.isEmpty())
@@ -1036,7 +1036,7 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
@Override
public String toString() {
if (this.units.isEmpty() || this.prefixes.isEmpty())
@@ -1046,7 +1046,7 @@ public final class UnitDatabase {
"Infinite map of name-unit entries created from units %s and prefixes %s",
this.units, this.prefixes);
}
-
+
/**
* {@inheritDoc}
*
@@ -1064,12 +1064,12 @@ public final class UnitDatabase {
return this.values;
}
}
-
+
/**
* 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
@@ -1077,7 +1077,7 @@ public final class UnitDatabase {
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
@@ -1092,20 +1092,20 @@ public final class UnitDatabase {
// "1e3")
), "\\($1 $2\\)");
}
-
+
/**
* A regular expression that separates names and expressions in unit files.
*/
private static final Pattern NAME_EXPRESSION = Pattern
.compile("(\\S+)\\s+(\\S.*)");
-
+
/**
* Like normal string comparisons, but shorter strings are always less than
* longer strings.
*/
private static final Comparator<String> lengthFirstComparator = Comparator
.comparingInt(String::length).thenComparing(Comparator.naturalOrder());
-
+
/**
* The exponent operator
*
@@ -1132,7 +1132,7 @@ public final class UnitDatabase {
// not a number
throw new IllegalArgumentException("Exponents must be numbers.");
}
-
+
/**
* The exponent operator
*
@@ -1158,7 +1158,7 @@ public final class UnitDatabase {
// not a number
throw new IllegalArgumentException("Exponents must be numbers.");
}
-
+
/**
* @return true if entry represents a removable duplicate entry of map.
* @since 2021-05-22
@@ -1174,7 +1174,7 @@ public final class UnitDatabase {
}
return false;
}
-
+
/**
* The units in this system, excluding prefixes.
*
@@ -1182,7 +1182,7 @@ public final class UnitDatabase {
* @since v0.1.0
*/
private final Map<String, Unit> prefixlessUnits;
-
+
/**
* The unit prefixes in this system.
*
@@ -1190,7 +1190,7 @@ public final class UnitDatabase {
* @since v0.1.0
*/
private final Map<String, UnitPrefix> prefixes;
-
+
/**
* The dimensions in this system.
*
@@ -1198,7 +1198,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, ObjectProduct<BaseDimension>> dimensions;
-
+
/**
* A map mapping strings to units (including prefixes)
*
@@ -1206,7 +1206,15 @@ public final class UnitDatabase {
* @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
@@ -1218,7 +1226,7 @@ public final class UnitDatabase {
* {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))}
*/
private Predicate<List<UnitPrefix>> prefixRepetitionRule;
-
+
/**
* A parser that can parse unit expressions.
*
@@ -1227,13 +1235,14 @@ public final class UnitDatabase {
*/
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();
-
+ .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();
+
/**
* A parser that can parse unit value expressions.
*
@@ -1241,15 +1250,15 @@ public final class UnitDatabase {
*/
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)
- .build();
-
+ .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)
+ .build();
+
/**
* A parser that can parse unit prefix expressions
*
@@ -1258,15 +1267,15 @@ public final class UnitDatabase {
*/
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)
- .build();
-
+ .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)
+ .build();
+
/**
* A parser that can parse unit dimension expressions.
*
@@ -1275,14 +1284,14 @@ public final class UnitDatabase {
*/
private final ExpressionParser<ObjectProduct<BaseDimension>> unitDimensionParser = new ExpressionParser.Builder<>(
this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
- .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2)
- .addNumericOperator("^", (o1, o2) -> {
- int exponent = (int) Math.round(o2.value());
- return o1.toExponent(exponent);
- }, 1).build();
-
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
+ .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2)
+ .addNumericOperator("^", (o1, o2) -> {
+ final int exponent = (int) Math.round(o2.value());
+ return o1.toExponent(exponent);
+ }, 1).build();
+
/**
* Creates the {@code UnitsDatabase}.
*
@@ -1292,7 +1301,7 @@ public final class UnitDatabase {
public UnitDatabase() {
this(prefixes -> true);
}
-
+
/**
* Creates the {@code UnitsDatabase}
*
@@ -1309,8 +1318,9 @@ 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.
*
@@ -1328,7 +1338,7 @@ public final class UnitDatabase {
.withName(dimension.getNameSymbol().withExtraName(name));
this.dimensions.put(name, namedDimension);
}
-
+
/**
* Adds to the list from a line in a unit dimension file.
*
@@ -1347,7 +1357,7 @@ public final class UnitDatabase {
lineCounter);
return;
}
-
+
// divide line into name and expression
final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
@@ -1356,12 +1366,12 @@ public final class UnitDatabase {
lineCounter));
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 expression is "!", search for an existing dimension
// if no unit found, throw an error
if (expression.equals("!")) {
@@ -1377,11 +1387,11 @@ public final class UnitDatabase {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
-
+
this.addDimension(name, dimension);
}
}
-
+
/**
* Adds a unit prefix to the database.
*
@@ -1398,7 +1408,7 @@ public final class UnitDatabase {
this.prefixes.put(Objects.requireNonNull(name, "name must not be null."),
namedPrefix);
}
-
+
/**
* Adds a unit to the database.
*
@@ -1415,7 +1425,7 @@ public final class UnitDatabase {
this.prefixlessUnits.put(
Objects.requireNonNull(name, "name must not be null."), namedUnit);
}
-
+
/**
* Adds to the list from a line in a unit file.
*
@@ -1434,7 +1444,7 @@ public final class UnitDatabase {
lineCounter);
return;
}
-
+
// divide line into name and expression
final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
@@ -1442,15 +1452,15 @@ public final class UnitDatabase {
"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 String expression = lineMatcher.group(2);
-
+
// 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
if (expression.equals("!")) {
@@ -1469,6 +1479,29 @@ public final class UnitDatabase {
}
final String prefixName = name.substring(0, name.length() - 1);
this.addPrefix(prefixName, prefix);
+ } else if (expression.contains(";")) {
+ // it's a multi-unit
+ final String[] parts = expression.split(";");
+ final List<LinearUnit> units = new ArrayList<>(parts.length);
+ for (final String unitName : parts) {
+ final Unit unit;
+ try {
+ unit = this.getUnitFromExpression(unitName.trim());
+ } catch (final NoSuchElementException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
+ }
+
+ if (unit instanceof LinearUnit) {
+ units.add((LinearUnit) unit);
+ } else {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw new IllegalArgumentException(String.format(
+ "Unit '%s' is in a unit-set expression, but is not linear.",
+ unitName));
+ }
+ }
+ this.addUnitSet(name, units);
} else {
// it's a unit, get the unit
final Unit unit;
@@ -1483,9 +1516,20 @@ public final class UnitDatabase {
}
}
}
-
+
/**
- * 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
+ */
+ public void addUnitSet(String name, List<LinearUnit> value) {
+ this.unitSets.put(name, value);
+ }
+
+ /**
+ * Removes all units, unit sets, prefixes and dimensions from this database.
*
* @since 2022-02-26
*/
@@ -1493,8 +1537,9 @@ public final class UnitDatabase {
this.dimensions.clear();
this.prefixes.clear();
this.prefixlessUnits.clear();
+ this.unitSets.clear();
}
-
+
/**
* Tests if the database has a unit dimension with this name.
*
@@ -1506,7 +1551,7 @@ public final class UnitDatabase {
public boolean containsDimensionName(final String name) {
return this.dimensions.containsKey(name);
}
-
+
/**
* Tests if the database has a unit prefix with this name.
*
@@ -1518,7 +1563,7 @@ public final class UnitDatabase {
public boolean containsPrefixName(final String name) {
return this.prefixes.containsKey(name);
}
-
+
/**
* Tests if the database has a unit with this name, taking prefixes into
* consideration
@@ -1531,7 +1576,16 @@ public final class UnitDatabase {
public boolean containsUnitName(final String name) {
return this.units.containsKey(name);
}
-
+
+ /**
+ * Returns true iff there is a unit set with this name.
+ *
+ * @since 2024-08-16
+ */
+ public boolean containsUnitSetName(String name) {
+ return this.unitSets.containsKey(name);
+ }
+
/**
* @return a map mapping dimension names to dimensions
* @since 2019-04-13
@@ -1540,7 +1594,7 @@ public final class UnitDatabase {
public Map<String, ObjectProduct<BaseDimension>> dimensionMap() {
return Collections.unmodifiableMap(this.dimensions);
}
-
+
/**
* Evaluates a unit expression, following the same rules as
* {@link #getUnitFromExpression}.
@@ -1551,23 +1605,23 @@ public final class UnitDatabase {
*/
public LinearUnitValue evaluateUnitExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias, or a number with precision first
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"
@@ -1580,10 +1634,10 @@ public final class UnitDatabase {
+ modifiedExpression.substring(i + 2);
}
}
-
+
return this.unitValueExpressionParser.parseExpression(modifiedExpression);
}
-
+
/**
* Gets a unit dimension from the database using its name.
*
@@ -1601,7 +1655,7 @@ public final class UnitDatabase {
else
return dimension;
}
-
+
/**
* Uses the database's data to parse an expression into a unit dimension
* <p>
@@ -1624,24 +1678,24 @@ public final class UnitDatabase {
public ObjectProduct<BaseDimension> getDimensionFromExpression(
final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a dimension as an alias first
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);
}
-
+
/**
* Gets a unit. If it is linear, cast it to a LinearUnit and return it.
* Otherwise, throw an {@code IllegalArgumentException}.
@@ -1660,7 +1714,7 @@ public final class UnitDatabase {
if (parts.size() != 2)
throw new IllegalArgumentException(
"Format nonlinear units like: unit(value).");
-
+
// solve the function
final Unit unit = this.getUnit(parts.get(0));
final double value = Double.parseDouble(
@@ -1669,7 +1723,7 @@ public final class UnitDatabase {
} else {
// get a linear unit
final Unit unit = this.getUnit(name);
-
+
if (unit instanceof LinearUnit)
return (LinearUnit) unit;
else
@@ -1677,7 +1731,7 @@ public final class UnitDatabase {
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.
@@ -1695,7 +1749,7 @@ public final class UnitDatabase {
return LinearUnitValue.getExact(this.getLinearUnit(name), 1);
}
}
-
+
/**
* Gets a unit prefix from the database from its name
*
@@ -1716,7 +1770,7 @@ public final class UnitDatabase {
return prefix;
}
}
-
+
/**
* Gets all of the prefixes that are on a unit name, in application order.
*
@@ -1727,12 +1781,12 @@ public final class UnitDatabase {
List<UnitPrefix> getPrefixesFromName(final String unitName) {
final List<UnitPrefix> prefixes = new ArrayList<>();
String name = unitName;
-
+
while (!this.prefixlessUnits.containsKey(name)) {
// find the longest prefix
String longestPrefixName = null;
int longestLength = name.length();
-
+
while (longestPrefixName == null) {
longestLength--;
if (longestLength <= 0)
@@ -1742,7 +1796,7 @@ public final class UnitDatabase {
longestPrefixName = name.substring(0, longestLength);
}
}
-
+
// longest prefix found!
final UnitPrefix prefix = this.getPrefix(longestPrefixName);
prefixes.add(0, prefix);
@@ -1750,7 +1804,7 @@ public final class UnitDatabase {
}
return prefixes;
}
-
+
/**
* Gets a unit prefix from a prefix expression
* <p>
@@ -1767,24 +1821,24 @@ public final class UnitDatabase {
*/
public UnitPrefix getPrefixFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias first
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 the prefixRepetitionRule
* @since 2020-08-26
@@ -1792,7 +1846,7 @@ public final class UnitDatabase {
public final Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
return this.prefixRepetitionRule;
}
-
+
/**
* Gets a unit from the database from its name, looking for prefixes.
*
@@ -1825,9 +1879,9 @@ public final class UnitDatabase {
} else
return unit;
}
-
+
}
-
+
/**
* Uses the database's unit data to parse an expression into a unit
* <p>
@@ -1851,23 +1905,23 @@ public final class UnitDatabase {
*/
public Unit getUnitFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias first
if (this.containsUnitName(expression))
return this.getUnit(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)
for (int i = 0; i < modifiedExpression.length(); i++) {
@@ -1879,10 +1933,23 @@ public final class UnitDatabase {
+ modifiedExpression.substring(i + 2);
}
}
-
+
return this.unitExpressionParser.parseExpression(modifiedExpression);
}
-
+
+ /**
+ * Get a unit set from its name, throwing a {@link NoSuchElementException} if
+ * there is none.
+ *
+ * @since 2024-08-16
+ */
+ public List<LinearUnit> getUnitSet(String name) {
+ final List<LinearUnit> unitSet = this.unitSets.get(name);
+ if (unitSet == null)
+ throw new NoSuchElementException("No unit set with name " + name);
+ return unitSet;
+ }
+
/**
* Adds all dimensions from a file, using data from the database to parse
* them.
@@ -1922,7 +1989,7 @@ public final class UnitDatabase {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
-
+
/**
* Adds all dimensions from a {@code InputStream}. Otherwise, works like
* {@link #loadDimensionFile}.
@@ -1938,7 +2005,7 @@ public final class UnitDatabase {
}
}
}
-
+
/**
* Adds all units from a file, using data from the database to parse them.
* <p>
@@ -1977,7 +2044,7 @@ public final class UnitDatabase {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
-
+
/**
* Adds all units from a {@code InputStream}. Otherwise, works like
* {@link #loadUnitsFile}.
@@ -1993,7 +2060,7 @@ public final class UnitDatabase {
}
}
}
-
+
/**
* @param includeDuplicates if false, duplicates are removed from the map
* @return a map mapping prefix names to prefixes
@@ -2008,7 +2075,7 @@ public final class UnitDatabase {
.conditionalExistenceMap(this.prefixes,
entry -> !isRemovableDuplicate(this.prefixes, entry)));
}
-
+
/**
* @param prefixRepetitionRule the prefixRepetitionRule to set
* @since 2020-08-26
@@ -2017,7 +2084,7 @@ public final class UnitDatabase {
Predicate<List<UnitPrefix>> prefixRepetitionRule) {
this.prefixRepetitionRule = prefixRepetitionRule;
}
-
+
/**
* @return a string stating the number of units, prefixes and dimensions in
* the database
@@ -2029,7 +2096,7 @@ public final class UnitDatabase {
this.prefixlessUnits.size(), this.prefixes.size(),
this.dimensions.size());
}
-
+
/**
* Returns a map mapping unit names to units, including units with prefixes.
* <p>
@@ -2061,7 +2128,7 @@ public final class UnitDatabase {
return this.units; // PrefixedUnitMap is immutable so I don't need to make
// an unmodifiable map.
}
-
+
/**
* @param includeDuplicates if true, duplicate units will all exist in the
* map; if false, only one of each unit will exist,
@@ -2079,4 +2146,12 @@ public final class UnitDatabase {
entry -> !isRemovableDuplicate(this.prefixlessUnits,
entry)));
}
+
+ /**
+ * @return an unmodifiable map mapping names to unit sets
+ * @since 2024-08-16
+ */
+ public Map<String, List<LinearUnit>> unitSetMap() {
+ return Collections.unmodifiableMap(this.unitSets);
+ }
}
diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java
index 8f99649..4512e01 100644
--- a/src/main/java/sevenUnitsGUI/Presenter.java
+++ b/src/main/java/sevenUnitsGUI/Presenter.java
@@ -46,6 +46,7 @@ import sevenUnits.unit.UnitDatabase;
import sevenUnits.unit.UnitPrefix;
import sevenUnits.unit.UnitType;
import sevenUnits.unit.UnitValue;
+import sevenUnits.utils.NameSymbol;
import sevenUnits.utils.Nameable;
import sevenUnits.utils.ObjectProduct;
import sevenUnits.utils.UncertainDouble;
@@ -558,17 +559,13 @@ public final class Presenter {
}
// convert strings to data, checking if anything is invalid
- final Unit fromUnit, toUnit;
+ final Unit fromUnit;
final UncertainDouble uncertainValue;
if (this.database.containsUnitName(fromUnitString)) {
fromUnit = this.database.getUnit(fromUnitString);
} else
throw this.viewError("Nonexistent From unit: %s", fromUnitString);
- if (this.database.containsUnitName(toUnitString)) {
- toUnit = this.database.getUnit(toUnitString);
- } else
- throw this.viewError("Nonexistent To unit: %s", toUnitString);
try {
uncertainValue = UncertainDouble
.fromRoundedString(inputValueString);
@@ -577,38 +574,81 @@ public final class Presenter {
"Invalid value " + inputValueString);
return;
}
-
+ if (this.database.containsUnitName(toUnitString)) {
+ final Unit toUnit = this.database.getUnit(toUnitString);
+ ucview.showUnitConversionOutput(
+ this.convertUnitToUnit(fromUnitString, toUnitString,
+ inputValueString, fromUnit, toUnit, uncertainValue));
+ } else if (this.database.containsUnitSetName(toUnitString)) {
+ final List<LinearUnit> toMulti = this.database
+ .getUnitSet(toUnitString);
+ ucview.showUnitConversionOutput(
+ this.convertUnitToMulti(fromUnitString, inputValueString,
+ fromUnit, toMulti, uncertainValue));
+ } else
+ throw this.viewError("Nonexistent To unit: %s", toUnitString);
+ } else
+ throw new UnsupportedOperationException(
+ "This function can only be called when the view is a UnitConversionView.");
+ }
+
+ private UnitConversionRecord convertUnitToMulti(String fromUnitString,
+ String inputValueString, Unit fromUnit, List<LinearUnit> toMulti,
+ UncertainDouble uncertainValue) {
+ for (final LinearUnit toUnit : toMulti) {
if (!fromUnit.canConvertTo(toUnit))
throw this.viewError("Could not convert between %s and %s",
fromUnit, toUnit);
-
- // convert - we will need to erase uncertainty for non-linear units, so
- // we need to treat linear and non-linear units differently
- final String outputValueString;
- if (fromUnit instanceof LinearUnit && toUnit instanceof LinearUnit) {
- final LinearUnit fromLinear = (LinearUnit) fromUnit;
- final LinearUnit toLinear = (LinearUnit) toUnit;
- final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear,
- uncertainValue);
- final LinearUnitValue converted = initialValue.convertTo(toLinear);
-
- outputValueString = this.numberDisplayRule
- .apply(converted.getValue());
- } else {
- final UnitValue initialValue = UnitValue.of(fromUnit,
- uncertainValue.value());
- final UnitValue converted = initialValue.convertTo(toUnit);
-
- outputValueString = this.numberDisplayRule
- .apply(UncertainDouble.of(converted.getValue(), 0));
- }
+ }
+
+ final LinearUnitValue initValue;
+ if (fromUnit instanceof LinearUnit) {
+ final var fromLinear = (LinearUnit) fromUnit;
+ initValue = LinearUnitValue.of(fromLinear, uncertainValue);
+ } else {
+ initValue = UnitValue.of(fromUnit, uncertainValue.value())
+ .convertToBase(NameSymbol.EMPTY);
+ }
+
+ final List<LinearUnitValue> converted = initValue
+ .convertToMultiple(toMulti);
+ final String toExpression = converted.stream().map(
+ v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit())
+ .collect(Collectors.joining(" + "));
+ return UnitConversionRecord.valueOf(fromUnitString, toExpression,
+ inputValueString, "");
+
+ }
+
+ private UnitConversionRecord convertUnitToUnit(String fromUnitString,
+ String toUnitString, String inputValueString, Unit fromUnit,
+ Unit toUnit, UncertainDouble uncertainValue) {
+ if (!fromUnit.canConvertTo(toUnit))
+ throw this.viewError("Could not convert between %s and %s", fromUnit,
+ toUnit);
- ucview.showUnitConversionOutput(
- UnitConversionRecord.valueOf(fromUnitString, toUnitString,
- inputValueString, outputValueString));
- } else
- throw new UnsupportedOperationException(
- "This function can only be called when the view is a UnitConversionView.");
+ // convert - we will need to erase uncertainty for non-linear units, so
+ // we need to treat linear and non-linear units differently
+ final String outputValueString;
+ if (fromUnit instanceof LinearUnit && toUnit instanceof LinearUnit) {
+ final LinearUnit fromLinear = (LinearUnit) fromUnit;
+ final LinearUnit toLinear = (LinearUnit) toUnit;
+ final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear,
+ uncertainValue);
+ final LinearUnitValue converted = initialValue.convertTo(toLinear);
+
+ outputValueString = this.numberDisplayRule.apply(converted.getValue());
+ } else {
+ final UnitValue initialValue = UnitValue.of(fromUnit,
+ uncertainValue.value());
+ final UnitValue converted = initialValue.convertTo(toUnit);
+
+ outputValueString = this.numberDisplayRule
+ .apply(UncertainDouble.of(converted.getValue(), 0));
+ }
+
+ return UnitConversionRecord.valueOf(fromUnitString, toUnitString,
+ inputValueString, outputValueString);
}
/**
@@ -770,7 +810,7 @@ public final class Presenter {
* @return true iff the One-Way Conversion feature is available (views that
* show units as a list will have metric units removed from the From
* unit list and imperial/USC units removed from the To unit list)
- *
+ *
* @since 2022-03-30
*/
public boolean oneWayConversionEnabled() {
@@ -994,6 +1034,7 @@ public final class Presenter {
.entrySet().stream();
var toUnits = this.database.unitMapPrefixless(this.showDuplicates)
.entrySet().stream();
+ var unitSets = this.database.unitSetMap().entrySet().stream();
// filter by dimension, if one is selected
if (selectedDimensionName.isPresent()) {
@@ -1003,6 +1044,8 @@ public final class Presenter {
u -> viewDimension.equals(u.getValue().getDimension()));
toUnits = toUnits.filter(
u -> viewDimension.equals(u.getValue().getDimension()));
+ unitSets = unitSets.filter(us -> viewDimension
+ .equals(us.getValue().get(0).getDimension()));
}
// filter by unit type, if desired
@@ -1011,13 +1054,20 @@ public final class Presenter {
this::isSemiMetric) != UnitType.METRIC);
toUnits = toUnits.filter(u -> UnitType.getType(u.getValue(),
this::isSemiMetric) != UnitType.NON_METRIC);
+ // unit sets are never considered metric
+ unitSets = unitSets
+ .filter(us -> this.metricExceptions.contains(us.getKey()));
}
// set unit names
ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule)
.map(Map.Entry::getKey).collect(Collectors.toSet()));
- ucview.setToUnitNames(toUnits.flatMap(this::applySearchRule)
- .map(Map.Entry::getKey).collect(Collectors.toSet()));
+ final var toUnitNames = toUnits.flatMap(this::applySearchRule)
+ .map(Map.Entry::getKey);
+ final var unitSetNames = unitSets.map(Map.Entry::getKey);
+ final var toNames = Stream.concat(toUnitNames, unitSetNames)
+ .collect(Collectors.toSet());
+ ucview.setToUnitNames(toNames);
}
}
diff --git a/src/main/resources/metric_exceptions.txt b/src/main/resources/metric_exceptions.txt
index 73748c0..433dbb5 100644
--- a/src/main/resources/metric_exceptions.txt
+++ b/src/main/resources/metric_exceptions.txt
@@ -10,6 +10,7 @@ min
minute
h
hour
+hms
d
day
wk
diff --git a/src/main/resources/unitsfile.txt b/src/main/resources/unitsfile.txt
index 543c821..ecd9bb6 100644
--- a/src/main/resources/unitsfile.txt
+++ b/src/main/resources/unitsfile.txt
@@ -155,6 +155,7 @@ minute 60 second
min minute
hour 3600 second
h hour
+hms hour; minute; second
day 86400 second
d day
week 7 day
@@ -189,6 +190,8 @@ yard 3 foot
yd yard
mile 1760 yard
mi mile
+ftin foot; inch
+ydftin yard; foot; inch
# Compressed notation
kph km / hour