summaryrefslogtreecommitdiff
path: root/src/org/unitConverter/UnitsDatabase.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/unitConverter/UnitsDatabase.java')
-rw-r--r--src/org/unitConverter/UnitsDatabase.java1658
1 files changed, 0 insertions, 1658 deletions
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
deleted file mode 100644
index 520195c..0000000
--- a/src/org/unitConverter/UnitsDatabase.java
+++ /dev/null
@@ -1,1658 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-package org.unitConverter;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.AbstractSet;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-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 org.unitConverter.dimension.UnitDimension;
-import org.unitConverter.math.DecimalComparison;
-import org.unitConverter.math.ExpressionParser;
-import org.unitConverter.unit.DefaultUnitPrefix;
-import org.unitConverter.unit.LinearUnit;
-import org.unitConverter.unit.SI;
-import org.unitConverter.unit.Unit;
-import org.unitConverter.unit.UnitPrefix;
-
-/**
- * A database of units, prefixes and dimensions, and their names.
- *
- * @author Adrien Hopkins
- * @since 2019-01-07
- * @since v0.1.0
- */
-public final class UnitsDatabase {
- /**
- * A map for units that allows the use of prefixes.
- * <p>
- * As this map implementation is intended to be used as a sort of "augmented view" of a unit and prefix map, it is
- * unmodifiable but instead reflects the changes to the maps passed into it. Do not edit this map, instead edit the
- * maps that were passed in during construction.
- * </p>
- * <p>
- * The rules for applying prefixes onto units are the following:
- * <ul>
- * <li>Prefixes can only be applied to linear units.</li>
- * <li>Before attempting to search for prefixes in a unit name, this map will first search for a unit name. So, if
- * there are two units, "B" and "AB", and a prefix "A", this map will favour the unit "AB" over the unit "B" with
- * the prefix "A", even though they have the same string.</li>
- * <li>Longer prefixes are preferred to shorter prefixes. So, if you have units "BC" and "C", and prefixes "AB" and
- * "A", inputting "ABC" will return the unit "C" with the prefix "AB", not "BC" with the prefix "A".</li>
- * </ul>
- * </p>
- * <p>
- * This map is infinite in size if there is at least one unit and at least one prefix. If it is infinite, some
- * operations that only work with finite collections, like converting name/entry sets to arrays, will throw an
- * {@code IllegalStateException}.
- * </p>
- * <p>
- * 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
- */
- 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 set is infinite in size, {@link #toArray} will fail with a
- * {@code IllegalStateException} instead of creating an infinite-sized array.
- * </p>
- *
- * @author Adrien Hopkins
- * @since 2019-04-13
- * @since v0.2.0
- */
- private static final class PrefixedUnitEntrySet extends AbstractSet<Map.Entry<String, Unit>> {
- /**
- * The entry for this set.
- *
- * @author Adrien Hopkins
- * @since 2019-04-14
- * @since v0.2.0
- */
- private static final class PrefixedUnitEntry implements Entry<String, Unit> {
- private final String key;
- private final Unit value;
-
- /**
- * Creates the {@code PrefixedUnitEntry}.
- *
- * @param key
- * key
- * @param value
- * value
- * @since 2019-04-14
- * @since v0.2.0
- */
- public PrefixedUnitEntry(final String key, final Unit value) {
- this.key = key;
- this.value = value;
- }
-
- /**
- * @since 2019-05-03
- */
- @Override
- public boolean equals(final Object o) {
- if (!(o instanceof Map.Entry))
- return false;
- final Map.Entry<?, ?> other = (Map.Entry<?, ?>) o;
- 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
- */
- @Override
- public int hashCode() {
- return (this.getKey() == null ? 0 : this.getKey().hashCode())
- ^ (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 ({@code =}) character, then the string representation of the value.
- *
- * @since 2019-05-03
- */
- @Override
- public String toString() {
- return this.getKey() + "=" + this.getValue();
- }
- }
-
- /**
- * An iterator that iterates over the units of a {@code PrefixedUnitNameSet}.
- *
- * @author Adrien Hopkins
- * @since 2019-04-14
- * @since v0.2.0
- */
- private static final class PrefixedUnitEntryIterator implements Iterator<Entry<String, Unit>> {
- // position in the unit list
- 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}.
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- public PrefixedUnitEntryIterator(final PrefixedUnitMap map) {
- this.map = map;
- this.unitNames = new ArrayList<>(map.units.keySet());
- this.prefixNames = new ArrayList<>(map.prefixes.keySet());
- }
-
- /**
- * @return current unit name
- * @since 2019-04-14
- * @since v0.2.0
- */
- private String getCurrentUnitName() {
- final StringBuilder unitName = new StringBuilder();
- for (final int i : this.prefixCoordinates) {
- unitName.append(this.prefixNames.get(i));
- }
- unitName.append(this.unitNames.get(this.unitNamePosition));
-
- return unitName.toString();
- }
-
- @Override
- public boolean hasNext() {
- if (this.unitNames.isEmpty())
- return false;
- else {
- if (this.prefixNames.isEmpty())
- return this.unitNamePosition >= this.unitNames.size() - 1;
- else
- return true;
- }
- }
-
- /**
- * Changes this iterator's position to the next available one.
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- 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);
- } else {
- // get the prefix coordinate to increment, then increment
- 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()) {
- // carry over
- 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
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
- }
- }
- }
- }
- }
-
- @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
- * @since 2019-05-03
- */
- 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
- if (!this.prefixCoordinates.isEmpty()) {
- while (this.unitNamePosition < this.unitNames.size()
- && !(this.map.get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
- 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.
- *
- * @since 2019-05-03
- */
- @Override
- public String toString() {
- return String.format("Iterator iterating over name-unit entries; next value is \"%s\"",
- this.peek());
- }
- }
-
- // the map that created this set
- private final PrefixedUnitMap map;
-
- /**
- * Creates the {@code PrefixedUnitNameSet}.
- *
- * @param map
- * map that created this set
- * @since 2019-04-13
- * @since v0.2.0
- */
- 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.
- @SuppressWarnings("unchecked")
- final Entry<String, Unit> tempEntry = (Entry<String, Unit>) o;
- entry = tempEntry;
- } catch (final ClassCastException e) {
- 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)
- if (!this.contains(o))
- 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())
- return 0;
- else {
- if (this.map.prefixes.isEmpty())
- return this.map.units.size();
- else
- // infinite set
- return Integer.MAX_VALUE;
- }
- }
-
- /**
- * @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.");
- }
-
- /**
- * @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.");
- }
-
- @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);
- }
- }
-
- /**
- * 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 set is infinite in size, {@link #toArray} will fail with a
- * {@code IllegalStateException} instead of creating an infinite-sized array.
- * </p>
- *
- * @author Adrien Hopkins
- * @since 2019-04-13
- * @since v0.2.0
- */
- private static final class PrefixedUnitNameSet extends AbstractSet<String> {
- /**
- * An iterator that iterates over the units of a {@code PrefixedUnitNameSet}.
- *
- * @author Adrien Hopkins
- * @since 2019-04-14
- * @since v0.2.0
- */
- private static final class PrefixedUnitNameIterator implements Iterator<String> {
- // position in the unit list
- 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}.
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- public PrefixedUnitNameIterator(final PrefixedUnitMap map) {
- this.map = map;
- this.unitNames = new ArrayList<>(map.units.keySet());
- this.prefixNames = new ArrayList<>(map.prefixes.keySet());
- }
-
- /**
- * @return current unit name
- * @since 2019-04-14
- * @since v0.2.0
- */
- private String getCurrentUnitName() {
- final StringBuilder unitName = new StringBuilder();
- for (final int i : this.prefixCoordinates) {
- unitName.append(this.prefixNames.get(i));
- }
- unitName.append(this.unitNames.get(this.unitNamePosition));
-
- return unitName.toString();
- }
-
- @Override
- public boolean hasNext() {
- if (this.unitNames.isEmpty())
- return false;
- else {
- if (this.prefixNames.isEmpty())
- return this.unitNamePosition >= this.unitNames.size() - 1;
- else
- return true;
- }
- }
-
- /**
- * Changes this iterator's position to the next available one.
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- 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);
- } else {
- // get the prefix coordinate to increment, then increment
- 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()) {
- // carry over
- 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
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
- }
- }
- }
- }
- }
-
- @Override
- public String next() {
- final String nextName = this.peek();
-
- this.incrementPosition();
-
- return nextName;
- }
-
- /**
- * @return the next element in the iterator, without iterating over it
- * @since 2019-05-03
- */
- private String 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
- if (!this.prefixCoordinates.isEmpty()) {
- while (this.unitNamePosition < this.unitNames.size()
- && !(this.map.get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
- 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.
- *
- * @since 2019-05-03
- */
- @Override
- public String toString() {
- return String.format("Iterator iterating over unit names; next value is \"%s\"", this.peek());
- }
- }
-
- // the map that created this set
- private final PrefixedUnitMap map;
-
- /**
- * Creates the {@code PrefixedUnitNameSet}.
- *
- * @param map
- * map that created this set
- * @since 2019-04-13
- * @since v0.2.0
- */
- 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)
- if (!this.contains(o))
- 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())
- return 0;
- else {
- if (this.map.prefixes.isEmpty())
- return this.map.units.size();
- else
- // infinite set
- return Integer.MAX_VALUE;
- }
- }
-
- /**
- * @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.");
-
- }
-
- /**
- * @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.");
- }
-
- @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);
- }
- }
-
- /**
- * The units stored in this collection, without prefixes.
- *
- * @since 2019-04-13
- * @since v0.2.0
- */
- private final Map<String, Unit> units;
-
- /**
- * The available prefixes for use.
- *
- * @since 2019-04-13
- * @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}.
- *
- * @param units
- * map mapping unit names to units
- * @param prefixes
- * map mapping prefix names to prefixes
- * @since 2019-04-13
- * @since v0.2.0
- */
- public PrefixedUnitMap(final Map<String, Unit> units, final Map<String, UnitPrefix> prefixes) {
- // I am making unmodifiable maps to ensure I don't accidentally make changes.
- 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)
- // - it is longer than the existing largest prefix (since I am looking for the longest valid prefix)
- // - the part after the prefix is a valid unit name
- // - the unit described that name is a linear unit (since only linear units can have prefixes)
- if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) {
- final String rest = unitName.substring(prefixName.length());
- if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
- longestPrefix = prefixName;
- longestLength = prefixName.length();
- }
- }
- }
-
- return longestPrefix != null;
- }
-
- /**
- * {@inheritDoc}
- *
- * <p>
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method only tests for prefixless units.
- * </p>
- */
- @Override
- public boolean containsValue(final Object value) {
- return this.units.containsValue(value);
- }
-
- @Override
- public Set<Entry<String, Unit>> entrySet() {
- if (this.entrySet == null) {
- this.entrySet = new PrefixedUnitEntrySet(this);
- }
- 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)
- // - it is longer than the existing largest prefix (since I am looking for the longest valid prefix)
- // - the part after the prefix is a valid unit name
- // - the unit described that name is a linear unit (since only linear units can have prefixes)
- if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) {
- final String rest = unitName.substring(prefixName.length());
- if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
- longestPrefix = prefixName;
- longestLength = prefixName.length();
- }
- }
- }
-
- // 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);
- }
- }
-
- @Override
- public boolean isEmpty() {
- return this.units.isEmpty();
- }
-
- @Override
- public Set<String> keySet() {
- if (this.keySet == null) {
- this.keySet = new PrefixedUnitNameSet(this);
- }
- 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())
- return 0;
- else {
- if (this.prefixes.isEmpty())
- return this.units.size();
- else
- // infinite set
- return Integer.MAX_VALUE;
- }
- }
-
- @Override
- public String toString() {
- if (this.units.isEmpty() || this.prefixes.isEmpty())
- return super.toString();
- else
- 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.
- * </p>
- */
- @Override
- public Collection<Unit> values() {
- if (this.values == null) {
- this.values = Collections.unmodifiableCollection(this.units.values());
- }
- return this.values;
- }
- }
-
- /**
- * A regular expression that separates names and expressions in unit files.
- */
- private static final Pattern NAME_EXPRESSION = Pattern.compile("(\\S+)\\s+(\\S.*)");
-
- /**
- * 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, final LinearUnit exponentUnit) {
- // exponent function - first check if o2 is a number,
- if (exponentUnit.getBase().equals(SI.SI.getBaseUnit(UnitDimension.EMPTY))) {
- // 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.");
- }
-
- /**
- * The units in this system, excluding prefixes.
- *
- * @since 2019-01-07
- * @since v0.1.0
- */
- private final Map<String, Unit> prefixlessUnits;
-
- /**
- * The unit prefixes in this system.
- *
- * @since 2019-01-14
- * @since v0.1.0
- */
- private final Map<String, UnitPrefix> prefixes;
-
- /**
- * The dimensions in this system.
- *
- * @since 2019-03-14
- * @since v0.2.0
- */
- private final Map<String, UnitDimension> dimensions;
-
- /**
- * A map mapping strings to units (including prefixes)
- *
- * @since 2019-04-13
- * @since v0.2.0
- */
- private final Map<String, Unit> units;
-
- /**
- * 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("^", UnitsDatabase::exponentiateUnits, 2).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.times(o2), 0).addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
- .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), 1).build();
-
- /**
- * A parser that can parse unit dimension expressions.
- *
- * @since 2019-04-13
- * @since v0.2.0
- */
- private final ExpressionParser<UnitDimension> unitDimensionParser = new ExpressionParser.Builder<>(
- this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0).addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build();
-
- /**
- * Creates the {@code UnitsDatabase}.
- *
- * @since 2019-01-10
- * @since v0.1.0
- */
- public UnitsDatabase() {
- this.prefixlessUnits = new HashMap<>();
- this.prefixes = new HashMap<>();
- this.dimensions = new HashMap<>();
- this.units = new PrefixedUnitMap(this.prefixlessUnits, this.prefixes);
- }
-
- /**
- * 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
- * @since 2019-03-14
- * @since v0.2.0
- */
- public void addDimension(final String name, final UnitDimension dimension) {
- this.dimensions.put(Objects.requireNonNull(name, "name must not be null."),
- Objects.requireNonNull(dimension, "dimension must not be null."));
- }
-
- /**
- * 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
- * @since v0.2.0
- */
- private void addDimensionFromLine(final String line, final long lineCounter) {
- // ignore lines that start with a # sign - they're comments
- if (line.isEmpty())
- return;
- if (line.contains("#")) {
- this.addDimensionFromLine(line.substring(0, line.indexOf("#")), lineCounter);
- return;
- }
-
- // divide line into name and expression
- final Matcher 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);
-
- 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("!")) {
- if (!this.containsDimensionName(name))
- throw new IllegalArgumentException(
- String.format("! used but no dimension found (line %d).", lineCounter));
- } else {
- // it's a unit, get the unit
- final UnitDimension dimension;
- try {
- dimension = this.getDimensionFromExpression(expression);
- } catch (final IllegalArgumentException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
-
- this.addDimension(name, dimension);
- }
- }
-
- /**
- * 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
- * @since 2019-01-14
- * @since v0.1.0
- */
- public void addPrefix(final String name, final UnitPrefix prefix) {
- this.prefixes.put(Objects.requireNonNull(name, "name must not be null."),
- Objects.requireNonNull(prefix, "prefix must not be null."));
- }
-
- /**
- * Adds a unit to the database.
- *
- * @param name
- * unit's name
- * @param unit
- * unit to add
- * @throws NullPointerException
- * if unit is null
- * @since 2019-01-10
- * @since v0.1.0
- */
- public void addUnit(final String name, final Unit unit) {
- this.prefixlessUnits.put(Objects.requireNonNull(name, "name must not be null."),
- Objects.requireNonNull(unit, "unit must not be null."));
- }
-
- /**
- * 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
- * @since v0.2.0
- */
- private void addUnitOrPrefixFromLine(final String line, final long lineCounter) {
- // ignore lines that start with a # sign - they're comments
- if (line.isEmpty())
- return;
- if (line.contains("#")) {
- this.addUnitOrPrefixFromLine(line.substring(0, line.indexOf("#")), lineCounter);
- return;
- }
-
- // divide line into name and expression
- final Matcher 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 String expression = lineMatcher.group(2);
-
- 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("!")) {
- if (!this.containsUnitName(name))
- throw new IllegalArgumentException(String.format("! used but no unit found (line %d).", lineCounter));
- } else {
- if (name.endsWith("-")) {
- final UnitPrefix prefix;
- try {
- prefix = this.getPrefixFromExpression(expression);
- } catch (final IllegalArgumentException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
- this.addPrefix(name.substring(0, name.length() - 1), prefix);
- } else {
- // it's a unit, get the unit
- final Unit unit;
- try {
- unit = this.getUnitFromExpression(expression);
- } catch (final IllegalArgumentException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
-
- this.addUnit(name, unit);
- }
- }
- }
-
- /**
- * 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
- * @since v0.2.0
- */
- public boolean containsDimensionName(final String name) {
- return this.dimensions.containsKey(name);
- }
-
- /**
- * 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
- * @since v0.1.0
- */
- 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
- *
- * @param name
- * name to test
- * @return if database contains name
- * @since 2019-01-13
- * @since v0.1.0
- */
- public boolean containsUnitName(final String name) {
- return this.units.containsKey(name);
- }
-
- /**
- * @return a map mapping dimension names to dimensions
- * @since 2019-04-13
- * @since v0.2.0
- */
- public Map<String, UnitDimension> dimensionMap() {
- return Collections.unmodifiableMap(this.dimensions);
- }
-
- /**
- * Gets a unit dimension from the database using its name.
- *
- * <p>
- * This method accepts exponents, like "L^3"
- * </p>
- *
- * @param name
- * dimension's name
- * @return dimension
- * @since 2019-03-14
- * @since v0.2.0
- */
- public UnitDimension getDimension(final String name) {
- Objects.requireNonNull(name, "name must not be null.");
- if (name.contains("^")) {
- final String[] baseAndExponent = name.split("\\^");
-
- final UnitDimension base = this.getDimension(baseAndExponent[0]);
-
- final int exponent;
- try {
- exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponent must be an integer.");
- }
-
- return base.toExponent(exponent);
- }
- return this.dimensions.get(name);
- }
-
- /**
- * Uses the database's data to parse an expression into a unit dimension
- * <p>
- * The expression is a series of any of the following:
- * <ul>
- * <li>The name of a unit dimension, which multiplies or divides the result based on preceding operators</li>
- * <li>The operators '*' and '/', which multiply and divide (note that just putting two unit dimensions next to each
- * other is equivalent to multiplication)</li>
- * <li>The operator '^' which exponentiates. Exponents must be integers.</li>
- * </ul>
- *
- * @param expression
- * expression to parse
- * @throws IllegalArgumentException
- * if the expression cannot be parsed
- * @throws NullPointerException
- * if expression is null
- * @since 2019-04-13
- * @since v0.2.0
- */
- public UnitDimension 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;
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
-
- 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}.
- *
- * @param name
- * unit's name
- * @return unit
- * @since 2019-03-22
- * @since v0.2.0
- */
- private LinearUnit getLinearUnit(final String name) {
- // see if I am using a function-unit like tempC(100)
- if (name.contains("(") && name.contains(")")) {
- // break it into function name and value
- final List<String> parts = Arrays.asList(name.split("\\("));
- 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(parts.get(1).substring(0, parts.get(1).length() - 1));
- return unit.getBase().times(unit.convertToBase(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));
- }
- }
-
- /**
- * Gets a unit prefix from the database from its name
- *
- * @param name
- * prefix's name
- * @return prefix
- * @since 2019-01-10
- * @since v0.1.0
- */
- public UnitPrefix getPrefix(final String name) {
- try {
- return new DefaultUnitPrefix(Double.parseDouble(name));
- } catch (final NumberFormatException e) {
- return this.prefixes.get(name);
- }
- }
-
- /**
- * Gets a unit prefix from a prefix expression
- * <p>
- * 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
- * @throws NullPointerException
- * if any argument is null
- * @since 2019-01-14
- * @since v0.1.0
- */
- 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;
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
-
- return this.prefixExpressionParser.parseExpression(modifiedExpression);
- }
-
- /**
- * Gets a unit from the database from its name, looking for prefixes.
- *
- * @param name
- * unit's name
- * @return unit
- * @since 2019-01-10
- * @since v0.1.0
- */
- public Unit getUnit(final String name) {
- try {
- final double value = Double.parseDouble(name);
- return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(value);
- } catch (final NumberFormatException e) {
- return this.units.get(name);
- }
-
- }
-
- /**
- * Uses the database's unit data to parse an expression into a unit
- * <p>
- * The expression is a series of any of the following:
- * <ul>
- * <li>The name of a unit, which multiplies or divides the result based on preceding operators</li>
- * <li>The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
- * other is equivalent to multiplication)</li>
- * <li>The operator '^' which exponentiates. Exponents must be integers.</li>
- * <li>A number which is multiplied or divided</li>
- * </ul>
- * This method only works with linear units.
- *
- * @param expression
- * expression to parse
- * @throws IllegalArgumentException
- * if the expression cannot be parsed
- * @throws NullPointerException
- * if expression is null
- * @since 2019-01-07
- * @since v0.1.0
- */
- 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("-", " - ");
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
-
- // the previous operation breaks negative numbers, fix them!
- // (i.e. -2 becomes - 2)
- for (int i = 2; i < modifiedExpression.length(); i++) {
- if (modifiedExpression.charAt(i) == '-'
- && Arrays.asList('+', '-', '*', '/', '^').contains(modifiedExpression.charAt(i - 2))) {
- // found a broken negative number
- modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2);
- }
- }
-
- return this.unitExpressionParser.parseExpression(modifiedExpression);
- }
-
- /**
- * Adds all dimensions from a file, using data from the database to parse them.
- * <p>
- * Each line in the file should consist of a name and an expression (parsed by getDimensionFromExpression) separated
- * by any number of tab characters.
- * <p>
- * <p>
- * Allowed exceptions:
- * <ul>
- * <li>Anything after a '#' character is considered a comment and ignored.</li>
- * <li>Blank lines are also ignored</li>
- * <li>If an expression consists of a single exclamation point, instead of parsing it, this method will search the
- * database for an existing unit. If 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
- * @since 2019-01-13
- * @since v0.1.0
- */
- public void loadDimensionFile(final File file) {
- Objects.requireNonNull(file, "file must not be null.");
- try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
- // while the reader has lines to read, read a line, then parse it, then add it
- long lineCounter = 0;
- while (reader.ready()) {
- this.addDimensionFromLine(reader.readLine(), ++lineCounter);
- }
- } 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);
- }
- }
-
- /**
- * Adds all units from a file, using data from the database to parse them.
- * <p>
- * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
- * any number of tab characters.
- * <p>
- * <p>
- * Allowed exceptions:
- * <ul>
- * <li>Anything after a '#' character is considered a comment and ignored.</li>
- * <li>Blank lines are also ignored</li>
- * <li>If an expression consists of a single exclamation point, instead of parsing it, this method will search the
- * database for an existing unit. If 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
- * @since 2019-01-13
- * @since v0.1.0
- */
- public void loadUnitsFile(final File file) {
- Objects.requireNonNull(file, "file must not be null.");
- try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
- // while the reader has lines to read, read a line, then parse it, then add it
- long lineCounter = 0;
- while (reader.ready()) {
- this.addUnitOrPrefixFromLine(reader.readLine(), ++lineCounter);
- }
- } 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 a map mapping prefix names to prefixes
- * @since 2019-04-13
- * @since v0.2.0
- */
- public Map<String, UnitPrefix> prefixMap() {
- return Collections.unmodifiableMap(this.prefixes);
- }
-
- @Override
- public String toString() {
- return String.format("Unit Database with %d units and %d unit prefixes", this.prefixlessUnits.size(),
- this.prefixes.size());
- }
-
- /**
- * Returns a map mapping unit names to units, including units with prefixes.
- * <p>
- * The returned map is infinite in size if there is at least one unit and at least one prefix. If it is infinite,
- * some operations that only work with finite collections, like converting name/entry sets to arrays, will throw an
- * {@code IllegalStateException}.
- * </p>
- * <p>
- * Specifically, the operations that will throw an IllegalStateException if the map is infinite in size are:
- * <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
- */
- public Map<String, Unit> unitMap() {
- return this.units; // PrefixedUnitMap is immutable so I don't need to make an unmodifiable map.
- }
-
- /**
- * @return a map mapping unit names to units, ignoring prefixes
- * @since 2019-04-13
- * @since v0.2.0
- */
- public Map<String, Unit> unitMapPrefixless() {
- return Collections.unmodifiableMap(this.prefixlessUnits);
- }
-}