diff options
-rw-r--r-- | src/org/unitConverter/unit/MultiUnit.java | 160 | ||||
-rw-r--r-- | src/org/unitConverter/unit/MultiUnitTest.java | 106 | ||||
-rw-r--r-- | src/org/unitConverter/unit/UnitValue.java | 15 | ||||
-rw-r--r-- | src/org/unitConverter/unit/Unitlike.java | 6 | ||||
-rw-r--r-- | src/org/unitConverter/unit/UnitlikeValue.java | 24 |
5 files changed, 290 insertions, 21 deletions
diff --git a/src/org/unitConverter/unit/MultiUnit.java b/src/org/unitConverter/unit/MultiUnit.java new file mode 100644 index 0000000..a1623f8 --- /dev/null +++ b/src/org/unitConverter/unit/MultiUnit.java @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2020 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.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.unitConverter.math.ObjectProduct; + +/** + * A combination of units, like "5 foot + 7 inch". All but the last units should + * have a whole number value associated with them. + * + * @since 2020-10-02 + */ +public final class MultiUnit extends Unitlike<List<Double>> { + /** + * Creates a {@code MultiUnit} from its units. It will not have a name or + * symbol. + * + * @since 2020-10-03 + */ + public static final MultiUnit of(LinearUnit... units) { + return of(Arrays.asList(units)); + } + + /** + * Creates a {@code MultiUnit} from its units. It will not have a name or + * symbol. + * + * @since 2020-10-03 + */ + public static final MultiUnit of(List<LinearUnit> units) { + if (units.size() < 1) + throw new IllegalArgumentException("Must have at least one unit"); + final ObjectProduct<BaseUnit> unitBase = units.get(0).getBase(); + for (final LinearUnit unit : units) { + if (!unitBase.equals(unit.getBase())) + throw new IllegalArgumentException( + "All units must have the same base."); + } + return new MultiUnit(new ArrayList<>(units), unitBase, NameSymbol.EMPTY); + } + + /** + * The units that make up this value. + */ + private final List<LinearUnit> units; + + /** + * Creates a {@code MultiUnit}. + * + * @since 2020-10-03 + */ + private MultiUnit(List<LinearUnit> units, ObjectProduct<BaseUnit> unitBase, + NameSymbol ns) { + super(unitBase, ns); + this.units = units; + } + + @Override + protected List<Double> convertFromBase(double value) { + final List<Double> values = new ArrayList<>(this.units.size()); + double temp = value; + + for (final LinearUnit unit : this.units.subList(0, + this.units.size() - 1)) { + values.add(Math.floor(temp / unit.getConversionFactor())); + temp %= unit.getConversionFactor(); + } + + values.add(this.units.size() - 1, + this.units.get(this.units.size() - 1).convertFromBase(temp)); + + return values; + } + + /** + * Converts a value expressed in this unitlike form to a value expressed in + * {@code other}. + * + * @implSpec If conversion is possible, this implementation returns + * {@code other.convertFromBase(this.convertToBase(value))}. + * Therefore, overriding either of those methods will change the + * output of this method. + * + * @param other unit to convert to + * @param value value to convert + * @return converted value + * @since 2020-10-03 + * @throws IllegalArgumentException if {@code other} is incompatible for + * conversion with this unitlike form (as + * tested by {@link Unit#canConvertTo}). + * @throws NullPointerException if other is null + */ + public final <U extends Unitlike<V>, V> V convertTo(U other, + double... values) { + final List<Double> valueList = new ArrayList<>(values.length); + for (final double d : values) { + valueList.add(d); + } + + return this.convertTo(other, valueList); + } + + /** + * Converts a value expressed in this unitlike form to a value expressed in + * {@code other}. + * + * @implSpec If conversion is possible, this implementation returns + * {@code other.convertFromBase(this.convertToBase(value))}. + * Therefore, overriding either of those methods will change the + * output of this method. + * + * @param other unit to convert to + * @param value value to convert + * @return converted value + * @since 2020-10-03 + * @throws IllegalArgumentException if {@code other} is incompatible for + * conversion with this unitlike form (as + * tested by {@link Unit#canConvertTo}). + * @throws NullPointerException if other is null + */ + public final double convertTo(Unit other, double... values) { + final List<Double> valueList = new ArrayList<>(values.length); + for (final double d : values) { + valueList.add(d); + } + + return this.convertTo(other, valueList); + } + + @Override + protected double convertToBase(List<Double> value) { + if (value.size() != this.units.size()) + throw new IllegalArgumentException("Wrong number of values for " + + this.units.size() + "-unit MultiUnit."); + + double baseValue = 0; + for (int i = 0; i < this.units.size(); i++) { + baseValue += value.get(i) * this.units.get(i).getConversionFactor(); + } + return baseValue; + } +} diff --git a/src/org/unitConverter/unit/MultiUnitTest.java b/src/org/unitConverter/unit/MultiUnitTest.java new file mode 100644 index 0000000..5ea9d07 --- /dev/null +++ b/src/org/unitConverter/unit/MultiUnitTest.java @@ -0,0 +1,106 @@ +/** + * Copyright (C) 2020 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 static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.Test; + +/** + * Tests related to the {@code MultiUnit}. + * + * @since 2020-10-03 + */ +class MultiUnitTest { + + @Test + final void testConvert() { + final Random rng = ThreadLocalRandom.current(); + final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT, + BritishImperial.Length.INCH); + + assertEquals(1702.0, footInch.convertTo(SI.METRE.withPrefix(SI.MILLI), + Arrays.asList(5.0, 7.0)), 1.0); + + for (int i = 0; i < 1000; i++) { + final double feet = rng.nextInt(1000); + final double inches = rng.nextDouble() * 12; + final double millimetres = feet * 304.8 + inches * 25.4; + + final List<Double> feetAndInches = SI.METRE.withPrefix(SI.MILLI) + .convertTo(footInch, millimetres); + assertEquals(feet, feetAndInches.get(0), 1e-10); + assertEquals(inches, feetAndInches.get(1), 1e-10); + } + } + + /** + * Test method for + * {@link org.unitConverter.unit.MultiUnit#convertFromBase(double)}. + */ + @Test + final void testConvertFromBase() { + final Random rng = ThreadLocalRandom.current(); + final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT, + BritishImperial.Length.INCH); + + // 1.7 m =~ 5' + 7" + final List<Double> values = footInch.convertFromBase(1.7018); + + assertEquals(5, values.get(0)); + assertEquals(7, values.get(1), 1e-12); + + for (int i = 0; i < 1000; i++) { + final double feet = rng.nextInt(1000); + final double inches = rng.nextDouble() * 12; + final double metres = feet * 0.3048 + inches * 0.0254; + + final List<Double> feetAndInches = footInch.convertFromBase(metres); + assertEquals(feet, feetAndInches.get(0), 1e-10); + assertEquals(inches, feetAndInches.get(1), 1e-10); + } + } + + /** + * Test method for + * {@link org.unitConverter.unit.MultiUnit#convertToBase(java.util.List)}. + */ + @Test + final void testConvertToBase() { + final Random rng = ThreadLocalRandom.current(); + final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT, + BritishImperial.Length.INCH); + + // 1.7 m =~ 5' + 7" + assertEquals(1.7018, footInch.convertToBase(Arrays.asList(5.0, 7.0)), + 1e-12); + + for (int i = 0; i < 1000; i++) { + final double feet = rng.nextInt(1000); + final double inches = rng.nextDouble() * 12; + final double metres = feet * 0.3048 + inches * 0.0254; + + assertEquals(metres, + footInch.convertToBase(Arrays.asList(feet, inches)), 1e-12); + } + } +} diff --git a/src/org/unitConverter/unit/UnitValue.java b/src/org/unitConverter/unit/UnitValue.java index 8932ccc..c138332 100644 --- a/src/org/unitConverter/unit/UnitValue.java +++ b/src/org/unitConverter/unit/UnitValue.java @@ -70,15 +70,16 @@ public final class UnitValue { } /** - * Returns a UnitValue that represents the same value expressed in a - * different unit + * Returns a UnitlikeValue that represents the same value expressed in a + * different unitlike form. * * @param other new unit to express value in * @return value expressed in {@code other} */ - public final UnitValue convertTo(Unit other) { - return UnitValue.of(other, - this.getUnit().convertTo(other, this.getValue())); + public final <U extends Unitlike<W>, W> UnitlikeValue<U, W> convertTo( + U other) { + return UnitlikeValue.of(other, + this.unit.convertTo(other, this.getValue())); } /** @@ -88,8 +89,8 @@ public final class UnitValue { * @param other new unit to express value in * @return value expressed in {@code other} */ - public final <W> UnitlikeValue<W> convertTo(Unitlike<W> other) { - return UnitlikeValue.of(other, + public final UnitValue convertTo(Unit other) { + return UnitValue.of(other, this.getUnit().convertTo(other, this.getValue())); } diff --git a/src/org/unitConverter/unit/Unitlike.java b/src/org/unitConverter/unit/Unitlike.java index a6ddb04..8077771 100644 --- a/src/org/unitConverter/unit/Unitlike.java +++ b/src/org/unitConverter/unit/Unitlike.java @@ -102,7 +102,7 @@ public abstract class Unitlike<V> implements Nameable { * @param unitBase * @since 2020-09-07 */ - Unitlike(ObjectProduct<BaseUnit> unitBase, NameSymbol ns) { + protected Unitlike(ObjectProduct<BaseUnit> unitBase, NameSymbol ns) { this.unitBase = Objects.requireNonNull(unitBase, "unitBase may not be null"); this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); @@ -148,7 +148,7 @@ public abstract class Unitlike<V> implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -175,7 +175,7 @@ public abstract class Unitlike<V> implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unitlike form to convert to * @param value value to convert * @param <W> type of value to convert to diff --git a/src/org/unitConverter/unit/UnitlikeValue.java b/src/org/unitConverter/unit/UnitlikeValue.java index 669a123..79201c4 100644 --- a/src/org/unitConverter/unit/UnitlikeValue.java +++ b/src/org/unitConverter/unit/UnitlikeValue.java @@ -22,17 +22,18 @@ import java.util.Optional; * * @since 2020-09-07 */ -final class UnitlikeValue<V> { +final class UnitlikeValue<T extends Unitlike<V>, V> { /** * Gets a {@code UnitlikeValue<V>}. * * @since 2020-10-02 */ - public static <V> UnitlikeValue<V> of(Unitlike<V> unitlike, V value) { + public static <T extends Unitlike<V>, V> UnitlikeValue<T, V> of(T unitlike, + V value) { return new UnitlikeValue<>(unitlike, value); } - private final Unitlike<V> unitlike; + private final T unitlike; private final V value; /** @@ -40,7 +41,7 @@ final class UnitlikeValue<V> { * @param value * @since 2020-09-07 */ - private UnitlikeValue(Unitlike<V> unitlike, V value) { + private UnitlikeValue(T unitlike, V value) { this.unitlike = unitlike; this.value = value; } @@ -62,14 +63,15 @@ final class UnitlikeValue<V> { } /** - * Returns a UnitValue that represents the same value expressed in a - * different unit + * Returns a UnitlikeValue that represents the same value expressed in a + * different unitlike form. * * @param other new unit to express value in * @return value expressed in {@code other} */ - public final UnitValue convertTo(Unit other) { - return UnitValue.of(other, + public final <U extends Unitlike<W>, W> UnitlikeValue<U, W> convertTo( + U other) { + return UnitlikeValue.of(other, this.unitlike.convertTo(other, this.getValue())); } @@ -80,8 +82,8 @@ final class UnitlikeValue<V> { * @param other new unit to express value in * @return value expressed in {@code other} */ - public final <W> UnitlikeValue<W> convertTo(Unitlike<W> other) { - return UnitlikeValue.of(other, + public final UnitValue convertTo(Unit other) { + return UnitValue.of(other, this.unitlike.convertTo(other, this.getValue())); } @@ -114,7 +116,7 @@ final class UnitlikeValue<V> { return true; if (!(obj instanceof UnitlikeValue)) return false; - final UnitlikeValue<?> other = (UnitlikeValue<?>) obj; + final UnitlikeValue<?, ?> other = (UnitlikeValue<?, ?>) obj; if (this.getUnitlike() == null) { if (other.getUnitlike() != null) return false; |