summaryrefslogtreecommitdiff
path: root/src/org/unitConverter/unit/Unit.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/unitConverter/unit/Unit.java')
-rw-r--r--src/org/unitConverter/unit/Unit.java342
1 files changed, 203 insertions, 139 deletions
diff --git a/src/org/unitConverter/unit/Unit.java b/src/org/unitConverter/unit/Unit.java
index 35b32fc..0a3298f 100644
--- a/src/org/unitConverter/unit/Unit.java
+++ b/src/org/unitConverter/unit/Unit.java
@@ -16,15 +16,14 @@
*/
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;
+import org.unitConverter.math.DecimalComparison;
import org.unitConverter.math.ObjectProduct;
/**
@@ -33,211 +32,252 @@ 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.
+ * Returns a unit from its base and the functions it uses to convert to and
+ * from its base.
*
* <p>
- * For example, to get a unit representing the degree Celsius, the following code can be used:
+ * For example, to get a unit representing the degree Celsius, the following
+ * code can be used:
*
* {@code Unit.fromConversionFunctions(SI.KELVIN, tempK -> tempK - 273.15, tempC -> tempC + 273.15);}
* </p>
*
- * @param base
- * unit's base
- * @param converterFrom
- * function that accepts a value expressed in the unit's base and returns that value expressed in this
- * unit.
- * @param converterTo
- * function that accepts a value expressed in the unit and returns that value expressed in the unit's
- * base.
+ * @param base unit's base
+ * @param converterFrom function that accepts a value expressed in the unit's
+ * base and returns that value expressed in this unit.
+ * @param converterTo function that accepts a value expressed in the unit
+ * and returns that value expressed in the unit's base.
* @return a unit that uses the provided functions to convert.
* @since 2019-05-22
- * @throws NullPointerException
- * if any argument is null
+ * @throws NullPointerException if any argument is null
*/
- public static final Unit fromConversionFunctions(final ObjectProduct<BaseUnit> base,
- final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo) {
+ public static final Unit fromConversionFunctions(
+ final ObjectProduct<BaseUnit> base,
+ final DoubleUnaryOperator converterFrom,
+ final DoubleUnaryOperator converterTo) {
return new FunctionalUnit(base, converterFrom, converterTo);
}
-
+
/**
- * Returns a unit from its base and the functions it uses to convert to and from its base.
+ * Returns a unit from its base and the functions it uses to convert to and
+ * from its base.
*
* <p>
- * For example, to get a unit representing the degree Celsius, the following code can be used:
+ * For example, to get a unit representing the degree Celsius, the following
+ * code can be used:
*
* {@code Unit.fromConversionFunctions(SI.KELVIN, tempK -> tempK - 273.15, tempC -> tempC + 273.15);}
* </p>
*
- * @param base
- * unit's base
- * @param converterFrom
- * function that accepts a value expressed in the unit's base and returns that value expressed in this
- * unit.
- * @param converterTo
- * function that accepts a value expressed in the unit and returns that value expressed in the unit's
- * base.
- * @param ns
- * names and symbol of unit
+ * @param base unit's base
+ * @param converterFrom function that accepts a value expressed in the unit's
+ * base and returns that value expressed in this unit.
+ * @param converterTo function that accepts a value expressed in the unit
+ * and returns that value expressed in the unit's base.
+ * @param ns names and symbol of unit
* @return a unit that uses the provided functions to convert.
* @since 2019-05-22
- * @throws NullPointerException
- * if any argument is null
+ * @throws NullPointerException if any argument is null
*/
- public static final Unit fromConversionFunctions(final ObjectProduct<BaseUnit> base,
- final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo, final NameSymbol ns) {
+ public static final Unit fromConversionFunctions(
+ final ObjectProduct<BaseUnit> base,
+ final DoubleUnaryOperator converterFrom,
+ final DoubleUnaryOperator converterTo, final NameSymbol ns) {
return new FunctionalUnit(base, converterFrom, converterTo, ns);
}
-
+
/**
* The combination of units that this unit is based on.
*
* @since 2019-10-16
*/
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()
*
* @since 2019-10-16
*/
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
+ * @param unitBase base of unit
+ * @param ns names and symbol of unit
* @since 2019-10-16
- * @throws NullPointerException
- * if unitBase or ns is null
+ * @throws NullPointerException if unitBase or ns is null
*/
- protected Unit(final ObjectProduct<BaseUnit> unitBase, final 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();
+ Unit(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");
}
-
+
/**
* A constructor that constructs {@code BaseUnit} instances.
*
* @since 2019-10-16
*/
- Unit(final String primaryName, final String symbol, final Set<String> otherNames) {
+ Unit(final String primaryName, final String symbol,
+ final Set<String> otherNames) {
if (this instanceof BaseUnit) {
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));
}
-
+
/**
- * Checks if a value expressed in this unit can be converted to a value expressed in {@code other}
+ * @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
+ * @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 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.
+ * Converts from a value expressed in this unit's base unit to a value
+ * expressed in this unit.
* <p>
- * This must be the inverse of {@code convertToBase}, so {@code convertFromBase(convertToBase(value))} must be equal
- * to {@code value} for any value, ignoring precision loss by roundoff error.
+ * This must be the inverse of {@code convertToBase}, so
+ * {@code convertFromBase(convertToBase(value))} must be equal to
+ * {@code value} for any value, ignoring precision loss by roundoff error.
* </p>
* <p>
- * If this unit <i>is</i> a base unit, this method should return {@code value}.
+ * If this unit <i>is</i> a base unit, this method should return
+ * {@code value}.
* </p>
*
- * @implSpec This method is used by {@link #convertTo}, and its behaviour affects the behaviour of
- * {@code convertTo}.
+ * @implSpec This method is used by {@link #convertTo}, and its behaviour
+ * affects the behaviour of {@code convertTo}.
*
- * @param value
- * value expressed in <b>base</b> unit
+ * @param value value expressed in <b>base</b> unit
* @return value expressed in <b>this</b> unit
* @since 2018-12-22
* @since v0.1.0
*/
protected abstract double convertFromBase(double value);
-
+
/**
- * Converts a value expressed in this unit to a value expressed in {@code other}.
+ * Converts a value expressed in this unit to a value expressed in
+ * {@code other}.
*
* @implSpec If unit 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.
+ * {@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
+ * @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 unit (as tested by
- * {@link Unit#canConvertTo}).
- * @throws NullPointerException
- * if other is null
+ * @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 double convertTo(final Unit 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));
+ throw new IllegalArgumentException(
+ String.format("Cannot convert from %s to %s.", this, other));
}
-
+
+ /**
+ * 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.
+ * Converts from a value expressed in this unit to a value expressed in this
+ * unit's base unit.
* <p>
- * This must be the inverse of {@code convertFromBase}, so {@code convertToBase(convertFromBase(value))} must be
- * equal to {@code value} for any value, ignoring precision loss by roundoff error.
+ * This must be the inverse of {@code convertFromBase}, so
+ * {@code convertToBase(convertFromBase(value))} must be equal to
+ * {@code value} for any value, ignoring precision loss by roundoff error.
* </p>
* <p>
- * If this unit <i>is</i> a base unit, this method should return {@code value}.
+ * If this unit <i>is</i> a base unit, this method should return
+ * {@code value}.
* </p>
*
- * @implSpec This method is used by {@link #convertTo}, and its behaviour affects the behaviour of
- * {@code convertTo}.
+ * @implSpec This method is used by {@link #convertTo}, and its behaviour
+ * affects the behaviour of {@code convertTo}.
*
- * @param value
- * value expressed in <b>this</b> unit
+ * @param value value expressed in <b>this</b> unit
* @return value expressed in <b>base</b> unit
* @since 2018-12-22
* @since v0.1.0
*/
protected abstract double convertToBase(double value);
-
+
/**
* @return combination of units that this unit is based on
* @since 2018-12-22
@@ -246,7 +286,7 @@ public abstract class Unit {
public final ObjectProduct<BaseUnit> getBase() {
return this.unitBase;
}
-
+
/**
* @return dimension measured by this unit
* @since 2018-12-22
@@ -256,58 +296,82 @@ public abstract class Unit {
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 additionalNames
- * @since 2019-10-21
+ * @return the nameSymbol
+ * @since 2020-09-07
*/
- public final Set<String> getOtherNames() {
- return this.otherNames;
- }
-
- /**
- * @return primaryName
- * @since 2019-10-21
- */
- public final Optional<String> getPrimaryName() {
- return this.primaryName;
+ @Override
+ public final NameSymbol getNameSymbol() {
+ return this.nameSymbol;
}
-
+
/**
- * @return symbol
- * @since 2019-10-21
+ * Returns true iff this unit is metric.
+ * <p>
+ * "Metric" is defined by three conditions:
+ * <ul>
+ * <li>Must be an instance of {@link LinearUnit}.</li>
+ * <li>Must be based on the SI base units (as determined by getBase())</li>
+ * <li>The conversion factor must be a power of 10.</li>
+ * </ul>
+ * <p>
+ * Note that this definition excludes some units that many would consider
+ * "metric", such as the degree Celsius (fails the first condition),
+ * calories, minutes and hours (fail the third condition).
+ * <p>
+ * All SI units (as designated by the BIPM) except the degree Celsius are
+ * considered "metric" by this definition.
+ *
+ * @since 2020-08-27
*/
- public final Optional<String> getSymbol() {
- return this.symbol;
+ public final boolean isMetric() {
+ // first condition - check that it is a linear unit
+ if (!(this instanceof LinearUnit))
+ return false;
+ final LinearUnit linear = (LinearUnit) this;
+
+ // second condition - check that
+ for (final BaseUnit b : linear.getBase().getBaseSet()) {
+ if (!SI.BaseUnits.BASE_UNITS.contains(b))
+ return false;
+ }
+
+ // third condition - check that conversion factor is a power of 10
+ return DecimalComparison
+ .equals(Math.log10(linear.getConversionFactor()) % 1.0, 0);
}
-
+
@Override
public String toString() {
return this.getPrimaryName().orElse("Unnamed unit")
- + (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()));
+ + (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
+ * @param ns name(s) and symbol to use
* @return a copy of this unit with provided name(s) and symbol
* @since 2019-10-21
- * @throws NullPointerException
- * if ns is null
+ * @throws NullPointerException if ns is null
*/
public Unit withName(final NameSymbol ns) {
- return fromConversionFunctions(this.getBase(), this::convertFromBase, this::convertToBase,
+ return fromConversionFunctions(this.getBase(), this::convertFromBase,
+ this::convertToBase,
Objects.requireNonNull(ns, "ns must not be null."));
}
}