diff options
author | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2019-05-03 15:07:16 -0400 |
---|---|---|
committer | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2019-05-03 15:07:16 -0400 |
commit | 50a195ef78af5d15dd6e548d4d6928c281bbaac2 (patch) | |
tree | e87ac0509b7e79614a00cc62c88b64358143ad67 | |
parent | 2ce65fa76908d77a5e3b045a8eb40c798939b8be (diff) |
Added toString to UnitsDatabase and its helper classes.
-rwxr-xr-x | src/org/unitConverter/UnitsDatabase.java | 286 | ||||
-rw-r--r-- | src/test/java/UnitsDatabaseTest.java | 67 |
2 files changed, 295 insertions, 58 deletions
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java index e5d2f67..37d53d4 100755 --- a/src/org/unitConverter/UnitsDatabase.java +++ b/src/org/unitConverter/UnitsDatabase.java @@ -75,7 +75,11 @@ public final class UnitsDatabase { * <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 UnsupportedOperationException}. + * {@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 @@ -86,6 +90,12 @@ public final class UnitsDatabase { /** * 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 @@ -117,6 +127,18 @@ public final class UnitsDatabase { 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; @@ -127,9 +149,29 @@ public final class UnitsDatabase { 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(); + 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(); } } @@ -148,8 +190,8 @@ public final class UnitsDatabase { // values from the unit entry set private final Map<String, Unit> map; - private final List<String> unitNames; - private final List<String> prefixNames; + private transient final List<String> unitNames; + private transient final List<String> prefixNames; /** * Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -157,10 +199,10 @@ public final class UnitsDatabase { * @since 2019-04-14 * @since v0.2.0 */ - public PrefixedUnitEntryIterator(final PrefixedUnitEntrySet set) { - this.map = set.map; - this.unitNames = new ArrayList<>(set.map.units.keySet()); - this.prefixNames = new ArrayList<>(set.map.prefixes.keySet()); + public PrefixedUnitEntryIterator(final PrefixedUnitMap map) { + this.map = map; + this.unitNames = new ArrayList<>(map.units.keySet()); + this.prefixNames = new ArrayList<>(map.prefixes.keySet()); } /** @@ -228,8 +270,23 @@ public final class UnitsDatabase { @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()) { @@ -241,10 +298,20 @@ public final class UnitsDatabase { final String nextName = this.getCurrentUnitName(); - this.incrementPosition(); - 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 @@ -264,17 +331,17 @@ public final class UnitsDatabase { @Override public boolean add(final Map.Entry<String, Unit> e) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot add to an immutable set"); } @Override public boolean addAll(final Collection<? extends Map.Entry<String, Unit>> c) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot add to an immutable set"); } @Override public void clear() { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot clear an immutable set"); } @Override @@ -283,7 +350,7 @@ public final class UnitsDatabase { final Entry<String, Unit> entry; try { - // This is OK because I'm in a try-catch block. + // 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; @@ -309,27 +376,27 @@ public final class UnitsDatabase { @Override public Iterator<Entry<String, Unit>> iterator() { - return new PrefixedUnitEntryIterator(this); + return new PrefixedUnitEntryIterator(this.map); } @Override public boolean remove(final Object o) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override public boolean removeAll(final Collection<?> c) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override public boolean removeIf(final Predicate<? super Entry<String, Unit>> filter) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override public boolean retainAll(final Collection<?> c) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override @@ -345,29 +412,51 @@ public final class UnitsDatabase { } } + /** + * @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 UnsupportedOperationException("Cannot make an infinite set into an array."); + 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 UnsupportedOperationException("Cannot make an infinite set into an array."); + 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 @@ -388,8 +477,8 @@ public final class UnitsDatabase { // values from the unit name set private final Map<String, Unit> map; - private final List<String> unitNames; - private final List<String> prefixNames; + private transient final List<String> unitNames; + private transient final List<String> prefixNames; /** * Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -397,10 +486,10 @@ public final class UnitsDatabase { * @since 2019-04-14 * @since v0.2.0 */ - public PrefixedUnitNameIterator(final PrefixedUnitNameSet set) { - this.map = set.map; - this.unitNames = new ArrayList<>(set.map.units.keySet()); - this.prefixNames = new ArrayList<>(set.map.prefixes.keySet()); + public PrefixedUnitNameIterator(final PrefixedUnitMap map) { + this.map = map; + this.unitNames = new ArrayList<>(map.units.keySet()); + this.prefixNames = new ArrayList<>(map.prefixes.keySet()); } /** @@ -468,6 +557,18 @@ public final class UnitsDatabase { @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 @@ -479,11 +580,18 @@ public final class UnitsDatabase { } } - final String nextName = this.getCurrentUnitName(); - - this.incrementPosition(); + return this.getCurrentUnitName(); + } - return 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 unit names; next value is \"%s\"", this.peek()); } } @@ -504,17 +612,17 @@ public final class UnitsDatabase { @Override public boolean add(final String e) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot add to an immutable set"); } @Override public boolean addAll(final Collection<? extends String> c) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot add to an immutable set"); } @Override public void clear() { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot clear an immutable set"); } @Override @@ -537,27 +645,27 @@ public final class UnitsDatabase { @Override public Iterator<String> iterator() { - return new PrefixedUnitNameIterator(this); + return new PrefixedUnitNameIterator(this.map); } @Override public boolean remove(final Object o) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override public boolean removeAll(final Collection<?> c) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override public boolean removeIf(final Predicate<? super String> filter) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override public boolean retainAll(final Collection<?> c) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove from an immutable set"); } @Override @@ -573,23 +681,40 @@ public final class UnitsDatabase { } } + /** + * @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 UnsupportedOperationException("Cannot make an infinite set into an array."); + 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 UnsupportedOperationException("Cannot make an infinite set into an array."); + 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); } } @@ -610,9 +735,9 @@ public final class UnitsDatabase { private final Map<String, UnitPrefix> prefixes; // caches - private Collection<Unit> values = null; - private Set<String> keySet = null; - private Set<Entry<String, Unit>> entrySet = null; + private transient Collection<Unit> values = null; + private transient Set<String> keySet = null; + private transient Set<Entry<String, Unit>> entrySet = null; /** * Creates the {@code PrefixedUnitMap}. @@ -632,24 +757,24 @@ public final class UnitsDatabase { @Override public void clear() { - throw new UnsupportedOperationException(); + 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(); + 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(); + 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(); + throw new UnsupportedOperationException("Cannot edit an immutable map"); } @Override @@ -685,6 +810,13 @@ public final class UnitsDatabase { 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); @@ -758,47 +890,47 @@ public final class UnitsDatabase { @Override public Unit merge(final String key, final Unit value, final BiFunction<? super Unit, ? super Unit, ? extends Unit> remappingFunction) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot merge into an immutable map"); } @Override public Unit put(final String key, final Unit value) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot add entries to an immutable map"); } @Override public void putAll(final Map<? extends String, ? extends Unit> m) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot add entries to an immutable map"); } @Override public Unit putIfAbsent(final String key, final Unit value) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot add entries to an immutable map"); } @Override public Unit remove(final Object key) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove entries from an immutable map"); } @Override public boolean remove(final Object key, final Object value) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Cannot remove entries from an immutable map"); } @Override public Unit replace(final String key, final Unit value) { - throw new UnsupportedOperationException(); + 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(); + 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(); + throw new UnsupportedOperationException("Cannot replace entries in an immutable map"); } @Override @@ -815,6 +947,22 @@ public final class UnitsDatabase { } @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()); @@ -1459,7 +1607,31 @@ public final class UnitsDatabase { 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 diff --git a/src/test/java/UnitsDatabaseTest.java b/src/test/java/UnitsDatabaseTest.java index 9222740..6d2247f 100644 --- a/src/test/java/UnitsDatabaseTest.java +++ b/src/test/java/UnitsDatabaseTest.java @@ -19,6 +19,7 @@ package test.java; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.Iterator; import java.util.Map; @@ -49,6 +50,8 @@ public class UnitsDatabaseTest { // used for testing expressions // J = U^2 * V / W^2 private static final LinearUnit J = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2)); + private static final LinearUnit K = SI.KELVIN; + private static final Unit NONLINEAR = new AbstractUnit(SI.METRE) { @Override @@ -70,6 +73,52 @@ public class UnitsDatabaseTest { private static final UnitPrefix BC = new DefaultUnitPrefix(11); /** + * Confirms that operations that shouldn't function for infinite databases throw an {@code IllegalStateException}. + * + * @since 2019-05-03 + */ + @Test + public void testInfiniteSetExceptions() { + // load units + final UnitsDatabase infiniteDatabase = new UnitsDatabase(); + + infiniteDatabase.addUnit("J", J); + infiniteDatabase.addUnit("K", K); + + infiniteDatabase.addPrefix("A", A); + infiniteDatabase.addPrefix("B", B); + infiniteDatabase.addPrefix("C", C); + + { + boolean exceptionThrown = false; + try { + infiniteDatabase.unitMap().entrySet().toArray(); + } catch (final IllegalStateException e) { + exceptionThrown = true; + // pass! + } finally { + if (!exceptionThrown) { + fail("No IllegalStateException thrown"); + } + } + } + + { + boolean exceptionThrown = false; + try { + infiniteDatabase.unitMap().keySet().toArray(); + } catch (final IllegalStateException e) { + exceptionThrown = true; + // pass! + } finally { + if (!exceptionThrown) { + fail("No IllegalStateException thrown"); + } + } + } + } + + /** * Test that prefixes correctly apply to units. * * @since 2019-04-14 @@ -189,11 +238,15 @@ public class UnitsDatabaseTest { final UnitsDatabase database = new UnitsDatabase(); database.addUnit("J", J); + database.addUnit("K", K); database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); + final int NUM_UNITS = database.unitMapPrefixless().size(); + final int NUM_PREFIXES = database.prefixMap().size(); + final Iterator<String> nameIterator = database.unitMap().keySet().iterator(); final Iterator<Entry<String, Unit>> entryIterator = database.unitMap().entrySet().iterator(); @@ -203,11 +256,12 @@ public class UnitsDatabaseTest { // loop 1000 times for (int i = 0; i < 1000; i++) { // expected length of next - if (unitsWithThisLengthSoFar >= (int) Math.pow(3, expectedLength - 1)) { + if (unitsWithThisLengthSoFar >= NUM_UNITS * (int) Math.pow(NUM_PREFIXES, expectedLength - 1)) { expectedLength++; unitsWithThisLengthSoFar = 0; } + // test that stuff is valid final String nextName = nameIterator.next(); final Unit nextUnit = database.getUnit(nextName); final Entry<String, Unit> nextEntry = entryIterator.next(); @@ -218,6 +272,17 @@ public class UnitsDatabaseTest { unitsWithThisLengthSoFar++; } + + // test toString for consistency + final String entryIteratorString = entryIterator.toString(); + for (int i = 0; i < 3; i++) { + assertEquals(entryIteratorString, entryIterator.toString()); + } + + final String nameIteratorString = nameIterator.toString(); + for (int i = 0; i < 3; i++) { + assertEquals(nameIteratorString, nameIterator.toString()); + } } /** |