summaryrefslogtreecommitdiff
path: root/src/org/unitConverter/unit/Unitlike.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/unitConverter/unit/Unitlike.java')
-rw-r--r--src/org/unitConverter/unit/Unitlike.java260
1 files changed, 260 insertions, 0 deletions
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."));
+ }
+}