summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2020-10-02 10:16:10 -0500
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2020-10-02 10:16:10 -0500
commitcd33f886dfbd35c0ee3d8cf5b553ea3481b0b3a1 (patch)
tree821f14b1ec307f78a0eb9b8bce48ead18cae4d70
parent5a7e8f6fcb175b238eb1d5481513b35039107a3e (diff)
Added Unitlike objects
-rw-r--r--src/org/unitConverter/math/UncertainDouble.java15
-rw-r--r--src/org/unitConverter/unit/FunctionalUnitlike.java72
-rw-r--r--src/org/unitConverter/unit/LinearUnit.java18
-rw-r--r--src/org/unitConverter/unit/LinearUnitValue.java22
-rw-r--r--src/org/unitConverter/unit/Nameable.java59
-rw-r--r--src/org/unitConverter/unit/Unit.java118
-rw-r--r--src/org/unitConverter/unit/UnitValue.java149
-rw-r--r--src/org/unitConverter/unit/Unitlike.java260
-rw-r--r--src/org/unitConverter/unit/UnitlikeValue.java172
9 files changed, 785 insertions, 100 deletions
diff --git a/src/org/unitConverter/math/UncertainDouble.java b/src/org/unitConverter/math/UncertainDouble.java
index e948df9..9601c75 100644
--- a/src/org/unitConverter/math/UncertainDouble.java
+++ b/src/org/unitConverter/math/UncertainDouble.java
@@ -1,5 +1,18 @@
/**
- * @since 2020-09-07
+ * 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.math;
diff --git a/src/org/unitConverter/unit/FunctionalUnitlike.java b/src/org/unitConverter/unit/FunctionalUnitlike.java
new file mode 100644
index 0000000..21c1fca
--- /dev/null
+++ b/src/org/unitConverter/unit/FunctionalUnitlike.java
@@ -0,0 +1,72 @@
+/**
+ * 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.function.DoubleFunction;
+import java.util.function.ToDoubleFunction;
+
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * A unitlike form that converts using two conversion functions.
+ *
+ * @since 2020-09-07
+ */
+final class FunctionalUnitlike<V> extends Unitlike<V> {
+ /**
+ * A function that accepts a value in the unitlike form's base and returns a
+ * value in the unitlike form.
+ *
+ * @since 2020-09-07
+ */
+ private final DoubleFunction<V> converterFrom;
+
+ /**
+ * A function that accepts a value in the unitlike form and returns a value
+ * in the unitlike form's base.
+ */
+ private final ToDoubleFunction<V> converterTo;
+
+ /**
+ * Creates the {@code FunctionalUnitlike}.
+ *
+ * @param base unitlike form's base
+ * @param converterFrom function that accepts a value in the unitlike form's
+ * base and returns a value in the unitlike form.
+ * @param converterTo function that accepts a value in the unitlike form
+ * and returns a value in the unitlike form's base.
+ * @throws NullPointerException if any argument is null
+ * @since 2019-05-22
+ */
+ protected FunctionalUnitlike(ObjectProduct<BaseUnit> unitBase, NameSymbol ns,
+ DoubleFunction<V> converterFrom, ToDoubleFunction<V> converterTo) {
+ super(unitBase, ns);
+ this.converterFrom = converterFrom;
+ this.converterTo = converterTo;
+ }
+
+ @Override
+ protected V convertFromBase(double value) {
+ return this.converterFrom.apply(value);
+ }
+
+ @Override
+ protected double convertToBase(V value) {
+ return this.converterTo.applyAsDouble(value);
+ }
+
+}
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
index 2d63ca7..b7f33d5 100644
--- a/src/org/unitConverter/unit/LinearUnit.java
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -65,6 +65,24 @@ public final class LinearUnit extends Unit {
}
/**
+ * @return the base unit associated with {@code unit}, as a
+ * {@code LinearUnit}.
+ * @since 2020-10-02
+ */
+ public static LinearUnit getBase(final Unit unit) {
+ return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY);
+ }
+
+ /**
+ * @return the base unit associated with {@code unitlike}, as a
+ * {@code LinearUnit}.
+ * @since 2020-10-02
+ */
+ public static LinearUnit getBase(final Unitlike<?> unit) {
+ return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY);
+ }
+
+ /**
* Gets a {@code LinearUnit} from a unit base and a conversion factor. In
* other words, gets the product of {@code unitBase} and
* {@code conversionFactor}, expressed as a {@code LinearUnit}.
diff --git a/src/org/unitConverter/unit/LinearUnitValue.java b/src/org/unitConverter/unit/LinearUnitValue.java
index d86d344..8de734e 100644
--- a/src/org/unitConverter/unit/LinearUnitValue.java
+++ b/src/org/unitConverter/unit/LinearUnitValue.java
@@ -1,5 +1,18 @@
/**
- *
+ * Copyright (C) 2019 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;
@@ -53,6 +66,7 @@ public final class LinearUnitValue {
}
private final LinearUnit unit;
+
private final UncertainDouble value;
/**
@@ -176,8 +190,7 @@ public final class LinearUnitValue {
/**
* @return the unit
- *
- * @since 2020-07-26
+ * @since 2020-09-29
*/
public final LinearUnit getUnit() {
return this.unit;
@@ -185,8 +198,7 @@ public final class LinearUnitValue {
/**
* @return the value
- *
- * @since 2020-07-26
+ * @since 2020-09-29
*/
public final UncertainDouble getValue() {
return this.value;
diff --git a/src/org/unitConverter/unit/Nameable.java b/src/org/unitConverter/unit/Nameable.java
new file mode 100644
index 0000000..36740ab
--- /dev/null
+++ b/src/org/unitConverter/unit/Nameable.java
@@ -0,0 +1,59 @@
+/**
+ * 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.Optional;
+import java.util.Set;
+
+/**
+ * An object that can hold one or more names, and possibly a symbol. The name
+ * and symbol data should be immutable.
+ *
+ * @since 2020-09-07
+ */
+public interface Nameable {
+ /**
+ * @return a {@code NameSymbol} that contains this object's primary name,
+ * symbol and other names
+ * @since 2020-09-07
+ */
+ NameSymbol getNameSymbol();
+
+ /**
+ * @return set of alternate names
+ * @since 2020-09-07
+ */
+ default Set<String> getOtherNames() {
+ return this.getNameSymbol().getOtherNames();
+ }
+
+ /**
+ * @return preferred name of object
+ * @since 2020-09-07
+ */
+ default Optional<String> getPrimaryName() {
+ return this.getNameSymbol().getPrimaryName();
+ }
+
+ /**
+ * @return short symbol representing object
+ * @since 2020-09-07
+ */
+ default Optional<String> getSymbol() {
+ return this.getNameSymbol().getSymbol();
+ }
+}
diff --git a/src/org/unitConverter/unit/Unit.java b/src/org/unitConverter/unit/Unit.java
index eb9b000..0a3298f 100644
--- a/src/org/unitConverter/unit/Unit.java
+++ b/src/org/unitConverter/unit/Unit.java
@@ -16,12 +16,10 @@
*/
package org.unitConverter.unit;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import java.util.function.DoubleUnaryOperator;
@@ -34,7 +32,7 @@ import org.unitConverter.math.ObjectProduct;
* @author Adrien Hopkins
* @since 2019-10-16
*/
-public abstract class Unit {
+public abstract class Unit implements Nameable {
/**
* Returns a unit from its base and the functions it uses to convert to and
* from its base.
@@ -98,19 +96,11 @@ public abstract class Unit {
private final ObjectProduct<BaseUnit> unitBase;
/**
- * The primary name used by this unit.
- */
- private final Optional<String> primaryName;
-
- /**
- * A short symbol used to represent this unit.
- */
- private final Optional<String> symbol;
-
- /**
- * A set of any additional names and/or spellings that the unit uses.
+ * This unit's name(s) and symbol
+ *
+ * @since 2020-09-07
*/
- private final Set<String> otherNames;
+ private final NameSymbol nameSymbol;
/**
* Cache storing the result of getDimension()
@@ -120,20 +110,17 @@ public abstract class Unit {
private transient ObjectProduct<BaseDimension> dimension = null;
/**
- * Creates the {@code AbstractUnit}.
+ * Creates the {@code Unit}.
*
* @param unitBase base of unit
* @param ns names and symbol of unit
* @since 2019-10-16
* @throws NullPointerException if unitBase or ns is null
*/
- protected Unit(final ObjectProduct<BaseUnit> unitBase, final NameSymbol ns) {
+ Unit(ObjectProduct<BaseUnit> unitBase, NameSymbol ns) {
this.unitBase = Objects.requireNonNull(unitBase,
- "unitBase must not be null.");
- this.primaryName = Objects.requireNonNull(ns, "ns must not be null.")
- .getPrimaryName();
- this.symbol = ns.getSymbol();
- this.otherNames = ns.getOtherNames();
+ "unitBase may not be null");
+ this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null");
}
/**
@@ -147,18 +134,25 @@ public abstract class Unit {
this.unitBase = ObjectProduct.oneOf((BaseUnit) this);
} else
throw new AssertionError();
- this.primaryName = Optional.of(primaryName);
- this.symbol = Optional.of(symbol);
- this.otherNames = Collections.unmodifiableSet(new HashSet<>(Objects
- .requireNonNull(otherNames, "additionalNames must not be null.")));
+ this.nameSymbol = NameSymbol.of(primaryName, symbol,
+ new HashSet<>(otherNames));
+ }
+
+ /**
+ * @return this unit as a {@link Unitlike}
+ * @since 2020-09-07
+ */
+ public final Unitlike<Double> asUnitlike() {
+ return Unitlike.fromConversionFunctions(this.getBase(),
+ this::convertFromBase, this::convertToBase, this.getNameSymbol());
}
/**
* Checks if a value expressed in this unit can be converted to a value
* expressed in {@code other}
*
- * @param other unit to test with
- * @return true if the units are compatible
+ * @param other unit or unitlike form to test with
+ * @return true if they are compatible
* @since 2019-01-13
* @since v0.1.0
* @throws NullPointerException if other is null
@@ -169,6 +163,21 @@ public abstract class Unit {
}
/**
+ * Checks if a value expressed in this unit can be converted to a value
+ * expressed in {@code other}
+ *
+ * @param other unit or unitlike form to test with
+ * @return true if they are compatible
+ * @since 2019-01-13
+ * @since v0.1.0
+ * @throws NullPointerException if other is null
+ */
+ public final <W> boolean canConvertTo(final Unitlike<W> other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ return Objects.equals(this.getBase(), other.getBase());
+ }
+
+ /**
* Converts from a value expressed in this unit's base unit to a value
* expressed in this unit.
* <p>
@@ -219,6 +228,34 @@ public abstract class Unit {
}
/**
+ * Converts a value expressed in this unit 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 unitlike form to convert to
+ * @param value value to convert
+ * @param <W> type of value to convert to
+ * @return converted value
+ * @since 2020-09-07
+ * @throws IllegalArgumentException if {@code other} is incompatible for
+ * conversion with this unit (as tested by
+ * {@link Unit#canConvertTo}).
+ * @throws NullPointerException if other is null
+ */
+ public final <W> W convertTo(final Unitlike<W> other, final double value) {
+ Objects.requireNonNull(other, "other must not be null.");
+ if (this.canConvertTo(other))
+ return other.convertFromBase(this.convertToBase(value));
+ else
+ throw new IllegalArgumentException(
+ String.format("Cannot convert from %s to %s.", this, other));
+ }
+
+ /**
* Converts from a value expressed in this unit to a value expressed in this
* unit's base unit.
* <p>
@@ -270,27 +307,12 @@ public abstract class Unit {
}
/**
- * @return additionalNames
- * @since 2019-10-21
- */
- public final Set<String> getOtherNames() {
- return this.otherNames;
- }
-
- /**
- * @return primaryName
- * @since 2019-10-21
+ * @return the nameSymbol
+ * @since 2020-09-07
*/
- public final Optional<String> getPrimaryName() {
- return this.primaryName;
- }
-
- /**
- * @return symbol
- * @since 2019-10-21
- */
- public final Optional<String> getSymbol() {
- return this.symbol;
+ @Override
+ public final NameSymbol getNameSymbol() {
+ return this.nameSymbol;
}
/**
diff --git a/src/org/unitConverter/unit/UnitValue.java b/src/org/unitConverter/unit/UnitValue.java
index 9e565d9..8932ccc 100644
--- a/src/org/unitConverter/unit/UnitValue.java
+++ b/src/org/unitConverter/unit/UnitValue.java
@@ -1,3 +1,19 @@
+/**
+ * Copyright (C) 2019 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.Objects;
@@ -14,60 +30,57 @@ import java.util.Optional;
*/
public final class UnitValue {
/**
+ * Creates a {@code UnitValue} from a unit and the associated value.
+ *
* @param unit unit to use
* @param value value to use
* @return {@code UnitValue} instance
*/
public static UnitValue of(Unit unit, double value) {
- return new UnitValue(Objects.requireNonNull(unit, "unit must not be null"), value);
+ return new UnitValue(
+ Objects.requireNonNull(unit, "unit must not be null"), value);
}
-
+
private final Unit unit;
private final double value;
-
+
/**
* @param unit the unit being used
* @param value the value being represented
*/
- private UnitValue(Unit unit, double value) {
+ private UnitValue(Unit unit, Double value) {
this.unit = unit;
this.value = value;
}
-
+
/**
- * @return the unit
+ * @return true if this value can be converted to {@code other}.
+ * @since 2020-10-01
*/
- public final Unit getUnit() {
- return unit;
+ public final boolean canConvertTo(Unit other) {
+ return this.unit.canConvertTo(other);
}
-
+
/**
- * @return the value
+ * @return true if this value can be converted to {@code other}.
+ * @since 2020-10-01
*/
- public final double getValue() {
- return value;
+ public final <W> boolean canConvertTo(Unitlike<W> other) {
+ return this.unit.canConvertTo(other);
}
-
+
/**
- * Converts this {@code UnitValue} into an equivalent {@code LinearUnitValue} by
- * using this unit's base unit.
+ * Returns a UnitValue that represents the same value expressed in a
+ * different unit
*
- * @param newName A new name for the base unit. Use {@link NameSymbol#EMPTY} if
- * you don't want one.
- */
- public final LinearUnitValue asLinearUnitValue(NameSymbol newName) {
- LinearUnit base = LinearUnit.valueOf(unit.getBase(), 1, newName);
- return LinearUnitValue.getExact(base, base.convertToBase(value));
- }
-
- /**
- * @param other a {@code Unit}
- * @return true iff this value can be represented with {@code other}.
+ * @param other new unit to express value in
+ * @return value expressed in {@code other}
*/
- public final boolean canConvertTo(Unit other) {
- return this.unit.canConvertTo(other);
+ public final UnitValue convertTo(Unit other) {
+ return UnitValue.of(other,
+ this.getUnit().convertTo(other, this.getValue()));
}
-
+
/**
* Returns a UnitValue that represents the same value expressed in a
* different unit
@@ -75,39 +88,83 @@ public final class UnitValue {
* @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 <W> UnitlikeValue<W> convertTo(Unitlike<W> other) {
+ return UnitlikeValue.of(other,
+ this.getUnit().convertTo(other, this.getValue()));
}
-
+
+ /**
+ * Returns this unit value represented as a {@code LinearUnitValue} with this
+ * unit's base unit as the base.
+ *
+ * @param ns name and symbol for the base unit, use NameSymbol.EMPTY if not
+ * needed.
+ * @since 2020-09-29
+ */
+ public final LinearUnitValue convertToBase(NameSymbol ns) {
+ final LinearUnit base = LinearUnit.getBase(this.unit).withName(ns);
+ return this.convertToLinear(base);
+ }
+
+ /**
+ * @return a {@code LinearUnitValue} that is equivalent to this value. It
+ * will have zero uncertainty.
+ * @since 2020-09-29
+ */
+ public final LinearUnitValue convertToLinear(LinearUnit other) {
+ return LinearUnitValue.getExact(other,
+ this.getUnit().convertTo(other, this.getValue()));
+ }
+
/**
- * Returns true if this and obj represent the same value, regardless of whether
- * or not they are expressed in the same unit. So (1000 m).equals(1 km) returns
- * true.
+ * Returns true if this and obj represent the same value, regardless of
+ * whether or not they are expressed in the same unit. So (1000 m).equals(1
+ * km) returns true.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof UnitValue))
return false;
final UnitValue other = (UnitValue) obj;
- return Objects.equals(this.unit.getBase(), other.unit.getBase())
- && Double.doubleToLongBits(this.unit.convertToBase(this.getValue())) == Double
- .doubleToLongBits(other.unit.convertToBase(other.getValue()));
+ return Objects.equals(this.getUnit().getBase(), other.getUnit().getBase())
+ && Double.doubleToLongBits(
+ this.getUnit().convertToBase(this.getValue())) == Double
+ .doubleToLongBits(
+ other.getUnit().convertToBase(other.getValue()));
}
-
+
+ /**
+ * @return the unit
+ * @since 2020-09-29
+ */
+ public final Unit getUnit() {
+ return this.unit;
+ }
+
+ /**
+ * @return the value
+ * @since 2020-09-29
+ */
+ public final double getValue() {
+ return this.value;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(this.unit.getBase(), this.unit.convertFromBase(this.getValue()));
+ return Objects.hash(this.getUnit().getBase(),
+ this.getUnit().convertFromBase(this.getValue()));
}
-
+
@Override
public String toString() {
- Optional<String> primaryName = this.getUnit().getPrimaryName();
- Optional<String> symbol = this.getUnit().getSymbol();
+ final Optional<String> primaryName = this.getUnit().getPrimaryName();
+ final Optional<String> symbol = this.getUnit().getSymbol();
if (primaryName.isEmpty() && symbol.isEmpty()) {
- double baseValue = this.getUnit().convertToBase(this.getValue());
- return String.format("%s unnamed unit (= %s %s)", this.getValue(), baseValue, this.getUnit().getBase());
+ final double baseValue = this.getUnit().convertToBase(this.getValue());
+ return String.format("%s unnamed unit (= %s %s)", this.getValue(),
+ baseValue, this.getUnit().getBase());
} else {
- String unitName = symbol.orElse(primaryName.get());
+ final String unitName = symbol.orElse(primaryName.get());
return this.getValue() + " " + unitName;
}
}
diff --git a/src/org/unitConverter/unit/Unitlike.java b/src/org/unitConverter/unit/Unitlike.java
new file mode 100644
index 0000000..a6ddb04
--- /dev/null
+++ b/src/org/unitConverter/unit/Unitlike.java
@@ -0,0 +1,260 @@
+/**
+ * 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.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.DoubleFunction;
+import java.util.function.ToDoubleFunction;
+
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * An object that can convert a value between multiple forms (instances of the
+ * object); like a unit but the "converted value" can be any type.
+ *
+ * @since 2020-09-07
+ */
+public abstract class Unitlike<V> implements Nameable {
+ /**
+ * Returns a unitlike form from its base and the functions it uses to convert
+ * to and from its base.
+ *
+ * @param base unitlike form's base
+ * @param converterFrom function that accepts a value expressed in the
+ * unitlike form's base and returns that value expressed
+ * in this unitlike form.
+ * @param converterTo function that accepts a value expressed in the
+ * unitlike form and returns that value expressed in the
+ * unit's base.
+ * @return a unitlike form that uses the provided functions to convert.
+ * @since 2020-09-07
+ * @throws NullPointerException if any argument is null
+ */
+ public static final <W> Unitlike<W> fromConversionFunctions(
+ final ObjectProduct<BaseUnit> base,
+ final DoubleFunction<W> converterFrom,
+ final ToDoubleFunction<W> converterTo) {
+ return new FunctionalUnitlike<>(base, NameSymbol.EMPTY, converterFrom,
+ converterTo);
+ }
+
+ /**
+ * Returns a unitlike form from its base and the functions it uses to convert
+ * to and from its base.
+ *
+ * @param base unitlike form's base
+ * @param converterFrom function that accepts a value expressed in the
+ * unitlike form's base and returns that value expressed
+ * in this unitlike form.
+ * @param converterTo function that accepts a value expressed in the
+ * unitlike form and returns that value expressed in the
+ * unit's base.
+ * @param ns names and symbol of unit
+ * @return a unitlike form that uses the provided functions to convert.
+ * @since 2020-09-07
+ * @throws NullPointerException if any argument is null
+ */
+ public static final <W> Unitlike<W> fromConversionFunctions(
+ final ObjectProduct<BaseUnit> base,
+ final DoubleFunction<W> converterFrom,
+ final ToDoubleFunction<W> converterTo, final NameSymbol ns) {
+ return new FunctionalUnitlike<>(base, ns, converterFrom, converterTo);
+ }
+
+ /**
+ * The combination of units that this unit is based on.
+ *
+ * @since 2019-10-16
+ */
+ private final ObjectProduct<BaseUnit> unitBase;
+
+ /**
+ * This unit's name(s) and symbol
+ *
+ * @since 2020-09-07
+ */
+ private final NameSymbol nameSymbol;
+
+ /**
+ * Cache storing the result of getDimension()
+ *
+ * @since 2019-10-16
+ */
+ private transient ObjectProduct<BaseDimension> dimension = null;
+
+ /**
+ * @param unitBase
+ * @since 2020-09-07
+ */
+ 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");
+ }
+
+ /**
+ * Checks if a value expressed in this unitlike form can be converted to a
+ * value expressed in {@code other}
+ *
+ * @param other unit or unitlike form to test with
+ * @return true if they are compatible
+ * @since 2019-01-13
+ * @since v0.1.0
+ * @throws NullPointerException if other is null
+ */
+ public final boolean canConvertTo(final Unit other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ return Objects.equals(this.getBase(), other.getBase());
+ }
+
+ /**
+ * Checks if a value expressed in this unitlike form can be converted to a
+ * value expressed in {@code other}
+ *
+ * @param other unit or unitlike form to test with
+ * @return true if they are compatible
+ * @since 2019-01-13
+ * @since v0.1.0
+ * @throws NullPointerException if other is null
+ */
+ public final <W> boolean canConvertTo(final Unitlike<W> other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ return Objects.equals(this.getBase(), other.getBase());
+ }
+
+ protected abstract V convertFromBase(double value);
+
+ /**
+ * 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 2019-05-22
+ * @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(final Unit other, final V value) {
+ Objects.requireNonNull(other, "other must not be null.");
+ if (this.canConvertTo(other))
+ return other.convertFromBase(this.convertToBase(value));
+ else
+ throw new IllegalArgumentException(
+ String.format("Cannot convert from %s to %s.", this, other));
+ }
+
+ /**
+ * 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 unitlike form to convert to
+ * @param value value to convert
+ * @param <W> type of value to convert to
+ * @return converted value
+ * @since 2020-09-07
+ * @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 <W> W convertTo(final Unitlike<W> other, final V value) {
+ Objects.requireNonNull(other, "other must not be null.");
+ if (this.canConvertTo(other))
+ return other.convertFromBase(this.convertToBase(value));
+ else
+ throw new IllegalArgumentException(
+ String.format("Cannot convert from %s to %s.", this, other));
+ }
+
+ protected abstract double convertToBase(V value);
+
+ /**
+ * @return combination of units that this unit is based on
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public final ObjectProduct<BaseUnit> getBase() {
+ return this.unitBase;
+ }
+
+ /**
+ * @return dimension measured by this unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public final ObjectProduct<BaseDimension> getDimension() {
+ if (this.dimension == null) {
+ final Map<BaseUnit, Integer> mapping = this.unitBase.exponentMap();
+ final Map<BaseDimension, Integer> dimensionMap = new HashMap<>();
+
+ for (final BaseUnit key : mapping.keySet()) {
+ dimensionMap.put(key.getBaseDimension(), mapping.get(key));
+ }
+
+ this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
+ }
+ return this.dimension;
+ }
+
+ /**
+ * @return the nameSymbol
+ * @since 2020-09-07
+ */
+ @Override
+ public final NameSymbol getNameSymbol() {
+ return this.nameSymbol;
+ }
+
+ @Override
+ public String toString() {
+ return this.getPrimaryName().orElse("Unnamed unitlike form")
+ + (this.getSymbol().isPresent()
+ ? String.format(" (%s)", this.getSymbol().get())
+ : "")
+ + ", derived from "
+ + this.getBase().toString(u -> u.getSymbol().get())
+ + (this.getOtherNames().isEmpty() ? ""
+ : ", also called " + String.join(", ", this.getOtherNames()));
+ }
+
+ /**
+ * @param ns name(s) and symbol to use
+ * @return a copy of this unitlike form with provided name(s) and symbol
+ * @since 2020-09-07
+ * @throws NullPointerException if ns is null
+ */
+ public Unitlike<V> withName(final NameSymbol ns) {
+ return fromConversionFunctions(this.getBase(), this::convertFromBase,
+ this::convertToBase,
+ Objects.requireNonNull(ns, "ns must not be null."));
+ }
+}
diff --git a/src/org/unitConverter/unit/UnitlikeValue.java b/src/org/unitConverter/unit/UnitlikeValue.java
new file mode 100644
index 0000000..669a123
--- /dev/null
+++ b/src/org/unitConverter/unit/UnitlikeValue.java
@@ -0,0 +1,172 @@
+/**
+ * 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.Optional;
+
+/**
+ *
+ * @since 2020-09-07
+ */
+final class UnitlikeValue<V> {
+ /**
+ * Gets a {@code UnitlikeValue<V>}.
+ *
+ * @since 2020-10-02
+ */
+ public static <V> UnitlikeValue<V> of(Unitlike<V> unitlike, V value) {
+ return new UnitlikeValue<>(unitlike, value);
+ }
+
+ private final Unitlike<V> unitlike;
+ private final V value;
+
+ /**
+ * @param unitlike
+ * @param value
+ * @since 2020-09-07
+ */
+ private UnitlikeValue(Unitlike<V> unitlike, V value) {
+ this.unitlike = unitlike;
+ this.value = value;
+ }
+
+ /**
+ * @return true if this value can be converted to {@code other}.
+ * @since 2020-10-01
+ */
+ public final boolean canConvertTo(Unit other) {
+ return this.unitlike.canConvertTo(other);
+ }
+
+ /**
+ * @return true if this value can be converted to {@code other}.
+ * @since 2020-10-01
+ */
+ public final <W> boolean canConvertTo(Unitlike<W> other) {
+ return this.unitlike.canConvertTo(other);
+ }
+
+ /**
+ * Returns a UnitValue that represents the same value expressed in a
+ * different unit
+ *
+ * @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.unitlike.convertTo(other, this.getValue()));
+ }
+
+ /**
+ * Returns a UnitValue that represents the same value expressed in a
+ * different unit
+ *
+ * @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,
+ this.unitlike.convertTo(other, this.getValue()));
+ }
+
+ /**
+ * Returns this unit value represented as a {@code LinearUnitValue} with this
+ * unit's base unit as the base.
+ *
+ * @param ns name and symbol for the base unit, use NameSymbol.EMPTY if not
+ * needed.
+ * @since 2020-09-29
+ */
+ public final LinearUnitValue convertToBase(NameSymbol ns) {
+ final LinearUnit base = LinearUnit.getBase(this.unitlike).withName(ns);
+ return this.convertToLinear(base);
+ }
+
+ /**
+ * @return a {@code LinearUnitValue} that is equivalent to this value. It
+ * will have zero uncertainty.
+ * @since 2020-09-29
+ */
+ public final LinearUnitValue convertToLinear(LinearUnit other) {
+ return LinearUnitValue.getExact(other,
+ this.getUnitlike().convertTo(other, this.getValue()));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof UnitlikeValue))
+ return false;
+ final UnitlikeValue<?> other = (UnitlikeValue<?>) obj;
+ if (this.getUnitlike() == null) {
+ if (other.getUnitlike() != null)
+ return false;
+ } else if (!this.getUnitlike().equals(other.getUnitlike()))
+ return false;
+ if (this.getValue() == null) {
+ if (other.getValue() != null)
+ return false;
+ } else if (!this.getValue().equals(other.getValue()))
+ return false;
+ return true;
+ }
+
+ /**
+ * @return the unitlike
+ * @since 2020-09-29
+ */
+ public final Unitlike<V> getUnitlike() {
+ return this.unitlike;
+ }
+
+ /**
+ * @return the value
+ * @since 2020-09-29
+ */
+ public final V getValue() {
+ return this.value;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + (this.getUnitlike() == null ? 0 : this.getUnitlike().hashCode());
+ result = prime * result
+ + (this.getValue() == null ? 0 : this.getValue().hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ final Optional<String> primaryName = this.getUnitlike().getPrimaryName();
+ final Optional<String> symbol = this.getUnitlike().getSymbol();
+ if (primaryName.isEmpty() && symbol.isEmpty()) {
+ final double baseValue = this.getUnitlike()
+ .convertToBase(this.getValue());
+ return String.format("%s unnamed unit (= %s %s)", this.getValue(),
+ baseValue, this.getUnitlike().getBase());
+ } else {
+ final String unitName = symbol.orElse(primaryName.get());
+ return this.getValue() + " " + unitName;
+ }
+ }
+}