diff options
| author | Adrien Hopkins <ahopk127@my.yorku.ca> | 2021-05-22 14:05:44 -0500 | 
|---|---|---|
| committer | Adrien Hopkins <ahopk127@my.yorku.ca> | 2021-05-22 14:05:44 -0500 | 
| commit | 8645325803f8580c823cc4c2cec2ad76906b52bb (patch) | |
| tree | fff893611dfd82aca4af5e677ab782045d5543d9 /src/org/unitConverter/unit/UnitDatabase.java | |
| parent | 184b7cc697ffc2dcbd49cfb3d0fd7b14bdac8803 (diff) | |
| parent | 277500e27010839e03659870bc5890f1535aa8c8 (diff) | |
Merge branch 'develop' into feature-settings-tab
Diffstat (limited to 'src/org/unitConverter/unit/UnitDatabase.java')
| -rw-r--r-- | src/org/unitConverter/unit/UnitDatabase.java | 1991 | 
1 files changed, 0 insertions, 1991 deletions
| diff --git a/src/org/unitConverter/unit/UnitDatabase.java b/src/org/unitConverter/unit/UnitDatabase.java deleted file mode 100644 index 000acf5..0000000 --- a/src/org/unitConverter/unit/UnitDatabase.java +++ /dev/null @@ -1,1991 +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.unit; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.math.BigDecimal; -import java.nio.file.Files; -import java.nio.file.Path; -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.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Set; -import 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.math.ConditionalExistenceCollections; -import org.unitConverter.math.DecimalComparison; -import org.unitConverter.math.ExpressionParser; -import org.unitConverter.math.ObjectProduct; -import org.unitConverter.math.UncertainDouble; - -/** - * A database of units, prefixes and dimensions, and their names. - *  - * @author Adrien Hopkins - * @since 2019-01-07 - * @since v0.1.0 - */ -public final class UnitDatabase { -	/** -	 * 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; -		} -	} -	 -	/** -	 * Replacements done to *all* expression types -	 */ -	private static final Map<Pattern, String> EXPRESSION_REPLACEMENTS = new HashMap<>(); -	 -	// add data to expression replacements -	static { -		// add spaces around operators -		for (final String operator : Arrays.asList("\\*", "/", "\\^")) { -			EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator), -					" " + operator + " "); -		} -		 -		// replace multiple spaces with a single space -		EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " "); -		// place brackets around any expression of the form "number unit", with or -		// without the space -		EXPRESSION_REPLACEMENTS.put(Pattern.compile("((?:-?[1-9]\\d*|0)" // integer -				+ "(?:\\.\\d+(?:[eE]\\d+))?)" // optional decimal point with numbers -														// after it -				+ "\\s*" // optional space(s) -				+ "([a-zA-Z]+(?:\\^\\d+)?" // any string of letters -				+ "(?:\\s+[a-zA-Z]+(?:\\^\\d+)?))" // optional other letters -				+ "(?!-?\\d)" // no number directly afterwards (avoids matching -									// "1e3") -		), "\\($1 $2\\)"); -	} -	 -	/** -	 * A regular expression that separates names and expressions in unit files. -	 */ -	private static final Pattern NAME_EXPRESSION = Pattern -			.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.ONE.getBase())) { -			// then check if it is an integer, -			final double exponent = exponentUnit.getConversionFactor(); -			if (DecimalComparison.equals(exponent % 1, 0)) -				// then exponentiate -				return base.toExponent((int) (exponent + 0.5)); -			else -				// not an integer -				throw new UnsupportedOperationException( -						"Decimal exponents are currently not supported."); -		} else -			// not a number -			throw new IllegalArgumentException("Exponents must be numbers."); -	} -	 -	/** -	 * The exponent operator -	 *  -	 * @param base         base of exponentiation -	 * @param exponentUnit exponent -	 * @return result -	 * @since 2020-08-04 -	 */ -	private static final LinearUnitValue exponentiateUnitValues( -			final LinearUnitValue base, final LinearUnitValue exponentValue) { -		// exponent function - first check if o2 is a number, -		if (exponentValue.canConvertTo(SI.ONE)) { -			// then check if it is an integer, -			final double exponent = exponentValue.getValueExact(); -			if (DecimalComparison.equals(exponent % 1, 0)) -				// then exponentiate -				return base.toExponent((int) (exponent + 0.5)); -			else -				// not an integer -				throw new UnsupportedOperationException( -						"Decimal exponents are currently not supported."); -		} else -			// not a number -			throw new IllegalArgumentException("Exponents must be numbers."); -	} -	 -	/** -	 * 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, ObjectProduct<BaseDimension>> dimensions; -	 -	/** -	 * A map mapping strings to units (including prefixes) -	 *  -	 * @since 2019-04-13 -	 * @since v0.2.0 -	 */ -	private final Map<String, Unit> units; -	 -	/** -	 * The rule that specifies when prefix repetition is allowed. It takes in one -	 * argument: a list of the prefixes being applied to the unit -	 * <p> -	 * The prefixes are inputted in <em>application order</em>. This means that -	 * testing whether "kilomegagigametre" is a valid unit is equivalent to -	 * running the following code (assuming all variables are defined correctly): -	 * <br> -	 * {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))} -	 */ -	private Predicate<List<UnitPrefix>> prefixRepetitionRule; -	 -	/** -	 * 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("^", UnitDatabase::exponentiateUnits, 2) -					.build(); -	 -	/** -	 * A parser that can parse unit value expressions. -	 *  -	 * @since 2020-08-04 -	 */ -	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("^", UnitDatabase::exponentiateUnitValues, 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<ObjectProduct<BaseDimension>> 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 UnitDatabase() { -		this(prefixes -> true); -	} -	 -	/** -	 * Creates the {@code UnitsDatabase} -	 *  -	 * @param prefixRepetitionRule the rule that determines when prefix -	 *                             repetition is allowed -	 * @since 2020-08-26 -	 */ -	public UnitDatabase(Predicate<List<UnitPrefix>> prefixRepetitionRule) { -		this.prefixlessUnits = new HashMap<>(); -		this.prefixes = new HashMap<>(); -		this.dimensions = new HashMap<>(); -		this.prefixRepetitionRule = prefixRepetitionRule; -		this.units = ConditionalExistenceCollections.conditionalExistenceMap( -				new PrefixedUnitMap(this.prefixlessUnits, this.prefixes), -				entry -> this.prefixRepetitionRule -						.test(this.getPrefixesFromName(entry.getKey()))); -	} -	 -	/** -	 * 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 ObjectProduct<BaseDimension> 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 ObjectProduct<BaseDimension> 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, ObjectProduct<BaseDimension>> dimensionMap() { -		return Collections.unmodifiableMap(this.dimensions); -	} -	 -	/** -	 * Evaluates a unit expression, following the same rules as -	 * {@link #getUnitFromExpression}. -	 *  -	 * @param expression expression to parse -	 * @return {@code LinearUnitValue} representing value of expression -	 * @since 2020-08-04 -	 */ -	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" -		for (int i = 0; i < modifiedExpression.length(); i++) { -			if (modifiedExpression.charAt(i) == '-' -					&& (i < 2 || Arrays.asList('+', '-', '*', '/', '^') -							.contains(modifiedExpression.charAt(i - 2)))) { -				// found a broken negative number -				modifiedExpression = modifiedExpression.substring(0, i + 1) -						+ modifiedExpression.substring(i + 2); -			} -		} -		 -		return this.unitValueExpressionParser.parseExpression(modifiedExpression); -	} -	 -	/** -	 * 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 ObjectProduct<BaseDimension> getDimension(final String name) { -		Objects.requireNonNull(name, "name must not be null."); -		if (name.contains("^")) { -			final String[] baseAndExponent = name.split("\\^"); -			 -			final ObjectProduct<BaseDimension> 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 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()); -		} -		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) -		Objects.requireNonNull(name, "name may not be null"); -		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 LinearUnit.fromUnitValue(unit, value); -		} else { -			// get a linear unit -			final Unit unit = this.getUnit(name); -			 -			if (unit instanceof LinearUnit) -				return (LinearUnit) unit; -			else -				throw new IllegalArgumentException( -						String.format("%s is not a linear unit.", name)); -		} -	} -	 -	/** -	 * Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be -	 * converted to their base units. -	 *  -	 * @param name name of unit -	 * @return {@code LinearUnitValue} instance -	 * @since 2020-08-04 -	 */ -	private LinearUnitValue getLinearUnitValue(final String name) { -		try { -			// try to parse it as a number - otherwise it is not a number! -			final BigDecimal number = new BigDecimal(name); -			 -			final double uncertainty = Math.pow(10, -number.scale()); -			return LinearUnitValue.of(SI.ONE, -					UncertainDouble.of(number.doubleValue(), uncertainty)); -		} catch (final NumberFormatException e) { -			return LinearUnitValue.getExact(this.getLinearUnit(name), 1); -		} -	} -	 -	/** -	 * 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 UnitPrefix.valueOf(Double.parseDouble(name)); -		} catch (final NumberFormatException e) { -			return this.prefixes.get(name); -		} -	} -	 -	/** -	 * Gets all of the prefixes that are on a unit name, in application order. -	 *  -	 * @param unitName name of unit -	 * @return prefixes -	 * @since 2020-08-26 -	 */ -	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) -					throw new AssertionError( -							"No prefix found in " + name + ", but it is not a unit!"); -				if (this.prefixes.containsKey(name.substring(0, longestLength))) { -					longestPrefixName = name.substring(0, longestLength); -				} -			} -			 -			// longest prefix found! -			final UnitPrefix prefix = this.getPrefix(longestPrefixName); -			prefixes.add(0, prefix); -			name = name.substring(longestLength); -		} -		return prefixes; -	} -	 -	/** -	 * 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; -		 -		// 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 -	 */ -	public final Predicate<List<UnitPrefix>> getPrefixRepetitionRule() { -		return this.prefixRepetitionRule; -	} -	 -	/** -	 * Gets a unit from the database from its name, looking for prefixes. -	 *  -	 * @param name unit's name -	 * @return unit -	 * @since 2019-01-10 -	 * @since v0.1.0 -	 */ -	public Unit getUnit(final String name) { -		try { -			final double value = Double.parseDouble(name); -			return SI.ONE.times(value); -		} catch (final NumberFormatException e) { -			final Unit unit = this.units.get(name); -			if (unit == null) -				throw new NoSuchElementException("No unit " + name); -			else if (unit.getPrimaryName().isEmpty()) -				return unit.withName(NameSymbol.ofName(name)); -			else if (!unit.getPrimaryName().get().equals(name)) { -				final Set<String> otherNames = new HashSet<>(unit.getOtherNames()); -				otherNames.add(unit.getPrimaryName().get()); -				return unit.withName(NameSymbol.ofNullable(name, -						unit.getSymbol().orElse(null), otherNames)); -			} else if (!unit.getOtherNames().contains(name)) { -				final Set<String> otherNames = new HashSet<>(unit.getOtherNames()); -				otherNames.add(name); -				return unit.withName( -						NameSymbol.ofNullable(unit.getPrimaryName().orElse(null), -								unit.getSymbol().orElse(null), otherNames)); -			} else -				return unit; -		} -		 -	} -	 -	/** -	 * 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("-", " - "); -		 -		// 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++) { -			if (modifiedExpression.charAt(i) == '-' -					&& (i < 2 || Arrays.asList('+', '-', '*', '/', '^') -							.contains(modifiedExpression.charAt(i - 2)))) { -				// found a broken negative number -				modifiedExpression = modifiedExpression.substring(0, i + 1) -						+ modifiedExpression.substring(i + 2); -			} -		} -		 -		return this.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 Path file) { -		Objects.requireNonNull(file, "file must not be null."); -		try { -			long lineCounter = 0; -			for (final String line : Files.readAllLines(file)) { -				this.addDimensionFromLine(line, ++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 Path file) { -		Objects.requireNonNull(file, "file must not be null."); -		try { -			long lineCounter = 0; -			for (final String line : Files.readAllLines(file)) { -				this.addUnitOrPrefixFromLine(line, ++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); -	} -	 -	/** -	 * @param prefixRepetitionRule the prefixRepetitionRule to set -	 * @since 2020-08-26 -	 */ -	public final void setPrefixRepetitionRule( -			Predicate<List<UnitPrefix>> prefixRepetitionRule) { -		this.prefixRepetitionRule = prefixRepetitionRule; -	} -	 -	/** -	 * @return a string stating the number of units, prefixes and dimensions in -	 *         the database -	 */ -	@Override -	public String toString() { -		return String.format( -				"Unit Database with %d units, %d unit prefixes and %d dimensions", -				this.prefixlessUnits.size(), this.prefixes.size(), -				this.dimensions.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); -	} -} | 
