diff options
author | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2020-10-02 10:16:10 -0500 |
---|---|---|
committer | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2020-10-02 10:16:10 -0500 |
commit | cd33f886dfbd35c0ee3d8cf5b553ea3481b0b3a1 (patch) | |
tree | 821f14b1ec307f78a0eb9b8bce48ead18cae4d70 | |
parent | 5a7e8f6fcb175b238eb1d5481513b35039107a3e (diff) |
Added Unitlike objects
-rw-r--r-- | src/org/unitConverter/math/UncertainDouble.java | 15 | ||||
-rw-r--r-- | src/org/unitConverter/unit/FunctionalUnitlike.java | 72 | ||||
-rw-r--r-- | src/org/unitConverter/unit/LinearUnit.java | 18 | ||||
-rw-r--r-- | src/org/unitConverter/unit/LinearUnitValue.java | 22 | ||||
-rw-r--r-- | src/org/unitConverter/unit/Nameable.java | 59 | ||||
-rw-r--r-- | src/org/unitConverter/unit/Unit.java | 118 | ||||
-rw-r--r-- | src/org/unitConverter/unit/UnitValue.java | 149 | ||||
-rw-r--r-- | src/org/unitConverter/unit/Unitlike.java | 260 | ||||
-rw-r--r-- | src/org/unitConverter/unit/UnitlikeValue.java | 172 |
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; + } + } +} |