summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2020-10-03 11:40:17 -0500
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2020-10-03 11:40:17 -0500
commit0169e644908c4285536d9d23ab82b0a3a46d9d8b (patch)
treea921546e3bf022fdc6cc47a2123cfc367fd46557
parentcd33f886dfbd35c0ee3d8cf5b553ea3481b0b3a1 (diff)
Added the MultiUnit
-rw-r--r--src/org/unitConverter/unit/MultiUnit.java160
-rw-r--r--src/org/unitConverter/unit/MultiUnitTest.java106
-rw-r--r--src/org/unitConverter/unit/UnitValue.java15
-rw-r--r--src/org/unitConverter/unit/Unitlike.java6
-rw-r--r--src/org/unitConverter/unit/UnitlikeValue.java24
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;