summaryrefslogtreecommitdiff
path: root/src/main/java/org/unitConverter/unit
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/unitConverter/unit')
-rw-r--r--src/main/java/org/unitConverter/unit/BaseDimension.java87
-rw-r--r--src/main/java/org/unitConverter/unit/BaseUnit.java133
-rw-r--r--src/main/java/org/unitConverter/unit/BritishImperial.java116
-rw-r--r--src/main/java/org/unitConverter/unit/FunctionalUnit.java109
-rw-r--r--src/main/java/org/unitConverter/unit/FunctionalUnitlike.java72
-rw-r--r--src/main/java/org/unitConverter/unit/LinearUnit.java441
-rw-r--r--src/main/java/org/unitConverter/unit/LinearUnitValue.java341
-rw-r--r--src/main/java/org/unitConverter/unit/MultiUnit.java160
-rw-r--r--src/main/java/org/unitConverter/unit/NameSymbol.java280
-rw-r--r--src/main/java/org/unitConverter/unit/Nameable.java59
-rw-r--r--src/main/java/org/unitConverter/unit/SI.java479
-rw-r--r--src/main/java/org/unitConverter/unit/USCustomary.java135
-rw-r--r--src/main/java/org/unitConverter/unit/Unit.java377
-rw-r--r--src/main/java/org/unitConverter/unit/UnitDatabase.java1991
-rw-r--r--src/main/java/org/unitConverter/unit/UnitPrefix.java242
-rw-r--r--src/main/java/org/unitConverter/unit/UnitValue.java172
-rw-r--r--src/main/java/org/unitConverter/unit/Unitlike.java260
-rw-r--r--src/main/java/org/unitConverter/unit/UnitlikeValue.java174
-rw-r--r--src/main/java/org/unitConverter/unit/package-info.java24
19 files changed, 5652 insertions, 0 deletions
diff --git a/src/main/java/org/unitConverter/unit/BaseDimension.java b/src/main/java/org/unitConverter/unit/BaseDimension.java
new file mode 100644
index 0000000..8e63a17
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/BaseDimension.java
@@ -0,0 +1,87 @@
+/**
+ * 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;
+
+/**
+ * A dimension that defines a {@code BaseUnit}
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class BaseDimension {
+ /**
+ * Gets a {@code BaseDimension} with the provided name and symbol.
+ *
+ * @param name
+ * name of dimension
+ * @param symbol
+ * symbol used for dimension
+ * @return dimension
+ * @since 2019-10-16
+ */
+ public static BaseDimension valueOf(final String name, final String symbol) {
+ return new BaseDimension(name, symbol);
+ }
+
+ /**
+ * The name of the dimension.
+ */
+ private final String name;
+ /**
+ * The symbol used by the dimension. Symbols should be short, generally one or two characters.
+ */
+ private final String symbol;
+
+ /**
+ * Creates the {@code BaseDimension}.
+ *
+ * @param name
+ * name of unit
+ * @param symbol
+ * symbol of unit
+ * @throws NullPointerException
+ * if any argument is null
+ * @since 2019-10-16
+ */
+ private BaseDimension(final String name, final String symbol) {
+ this.name = Objects.requireNonNull(name, "name must not be null.");
+ this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
+ }
+
+ /**
+ * @return name
+ * @since 2019-10-16
+ */
+ public final String getName() {
+ return this.name;
+ }
+
+ /**
+ * @return symbol
+ * @since 2019-10-16
+ */
+ public final String getSymbol() {
+ return this.symbol;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s (%s)", this.getName(), this.getSymbol());
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/BaseUnit.java b/src/main/java/org/unitConverter/unit/BaseUnit.java
new file mode 100644
index 0000000..6757bd0
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/BaseUnit.java
@@ -0,0 +1,133 @@
+/**
+ * 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.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A unit that other units are defined by.
+ * <p>
+ * Note that BaseUnits <b>must</b> have names and symbols. This is because they
+ * are used for toString code. Therefore, the Optionals provided by
+ * {@link #getPrimaryName} and {@link #getSymbol} will always contain a value.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class BaseUnit extends Unit {
+ /**
+ * Gets a base unit from the dimension it measures, its name and its symbol.
+ *
+ * @param dimension dimension measured by this unit
+ * @param name name of unit
+ * @param symbol symbol of unit
+ * @return base unit
+ * @since 2019-10-16
+ */
+ public static BaseUnit valueOf(final BaseDimension dimension,
+ final String name, final String symbol) {
+ return new BaseUnit(dimension, name, symbol, new HashSet<>());
+ }
+
+ /**
+ * Gets a base unit from the dimension it measures, its name and its symbol.
+ *
+ * @param dimension dimension measured by this unit
+ * @param name name of unit
+ * @param symbol symbol of unit
+ * @return base unit
+ * @since 2019-10-21
+ */
+ public static BaseUnit valueOf(final BaseDimension dimension,
+ final String name, final String symbol, final Set<String> otherNames) {
+ return new BaseUnit(dimension, name, symbol, otherNames);
+ }
+
+ /**
+ * The dimension measured by this base unit.
+ */
+ private final BaseDimension dimension;
+
+ /**
+ * Creates the {@code BaseUnit}.
+ *
+ * @param dimension dimension of unit
+ * @param primaryName name of unit
+ * @param symbol symbol of unit
+ * @throws NullPointerException if any argument is null
+ * @since 2019-10-16
+ */
+ private BaseUnit(final BaseDimension dimension, final String primaryName,
+ final String symbol, final Set<String> otherNames) {
+ super(primaryName, symbol, otherNames);
+ this.dimension = Objects.requireNonNull(dimension,
+ "dimension must not be null.");
+ }
+
+ /**
+ * Returns a {@code LinearUnit} with this unit as a base and a conversion
+ * factor of 1. This operation must be done in order to allow units to be
+ * created with operations.
+ *
+ * @return this unit as a {@code LinearUnit}
+ * @since 2019-10-16
+ */
+ public LinearUnit asLinearUnit() {
+ return LinearUnit.valueOf(this.getBase(), 1);
+ }
+
+ @Override
+ protected double convertFromBase(final double value) {
+ return value;
+ }
+
+ @Override
+ protected double convertToBase(final double value) {
+ return value;
+ }
+
+ /**
+ * @return dimension
+ * @since 2019-10-16
+ */
+ public final BaseDimension getBaseDimension() {
+ return this.dimension;
+ }
+
+ @Override
+ public String toString() {
+ return this.getPrimaryName().orElse("Unnamed unit")
+ + (this.getSymbol().isPresent()
+ ? String.format(" (%s)", this.getSymbol().get())
+ : "");
+ }
+
+ @Override
+ public BaseUnit withName(final NameSymbol ns) {
+ Objects.requireNonNull(ns, "ns must not be null.");
+ if (!ns.getPrimaryName().isPresent())
+ throw new IllegalArgumentException(
+ "BaseUnits must have primary names.");
+ if (!ns.getSymbol().isPresent())
+ throw new IllegalArgumentException("BaseUnits must have symbols.");
+ return BaseUnit.valueOf(this.getBaseDimension(),
+ ns.getPrimaryName().get(), ns.getSymbol().get(),
+ ns.getOtherNames());
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/BritishImperial.java b/src/main/java/org/unitConverter/unit/BritishImperial.java
new file mode 100644
index 0000000..ea23cd1
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/BritishImperial.java
@@ -0,0 +1,116 @@
+/**
+ * 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;
+
+/**
+ * A static utility class that contains units in the British Imperial system.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-21
+ */
+public final class BritishImperial {
+ /**
+ * Imperial units that measure area
+ *
+ * @author Adrien Hopkins
+ * @since 2019-11-08
+ */
+ public static final class Area {
+ public static final LinearUnit SQUARE_FOOT = Length.FOOT.toExponent(2);
+ public static final LinearUnit SQUARE_YARD = Length.YARD.toExponent(2);
+ public static final LinearUnit SQUARE_MILE = Length.MILE.toExponent(2);
+ public static final LinearUnit PERCH = Length.ROD.times(Length.ROD);
+ public static final LinearUnit ROOD = Length.ROD.times(Length.FURLONG);
+ public static final LinearUnit ACRE = Length.FURLONG.times(Length.CHAIN);
+ }
+
+ /**
+ * Imperial units that measure length
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-28
+ */
+ public static final class Length {
+ /**
+ * According to the International Yard and Pound of 1959, a yard is defined as exactly 0.9144 metres.
+ */
+ public static final LinearUnit YARD = SI.METRE.times(0.9144);
+ public static final LinearUnit FOOT = YARD.dividedBy(3);
+ public static final LinearUnit INCH = FOOT.dividedBy(12);
+ public static final LinearUnit THOU = INCH.dividedBy(1000);
+ public static final LinearUnit CHAIN = YARD.times(22);
+ public static final LinearUnit FURLONG = CHAIN.times(10);
+ public static final LinearUnit MILE = FURLONG.times(8);
+ public static final LinearUnit LEAGUE = MILE.times(3);
+
+ public static final LinearUnit NAUTICAL_MILE = SI.METRE.times(1852);
+ public static final LinearUnit CABLE = NAUTICAL_MILE.dividedBy(10);
+ public static final LinearUnit FATHOM = CABLE.dividedBy(100);
+
+ public static final LinearUnit ROD = YARD.times(5.5);
+ public static final LinearUnit LINK = ROD.dividedBy(25);
+ }
+
+ /**
+ * British Imperial units that measure mass.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-11-08
+ */
+ public static final class Mass {
+ public static final LinearUnit POUND = SI.GRAM.times(453.59237);
+ public static final LinearUnit OUNCE = POUND.dividedBy(16);
+ public static final LinearUnit DRACHM = POUND.dividedBy(256);
+ public static final LinearUnit GRAIN = POUND.dividedBy(7000);
+ public static final LinearUnit STONE = POUND.times(14);
+ public static final LinearUnit QUARTER = STONE.times(2);
+ public static final LinearUnit HUNDREDWEIGHT = QUARTER.times(4);
+ public static final LinearUnit LONG_TON = HUNDREDWEIGHT.times(20);
+ public static final LinearUnit SLUG = SI.KILOGRAM.times(14.59390294);
+ }
+
+ /**
+ * British Imperial units that measure volume
+ *
+ * @author Adrien Hopkins
+ * @since 2019-11-08
+ */
+ public static final class Volume {
+ public static final LinearUnit FLUID_OUNCE = SI.LITRE.withPrefix(SI.MILLI).times(28.4130625);
+ public static final LinearUnit GILL = FLUID_OUNCE.times(5);
+ public static final LinearUnit PINT = FLUID_OUNCE.times(20);
+ public static final LinearUnit QUART = PINT.times(2);
+ public static final LinearUnit GALLON = QUART.times(4);
+ public static final LinearUnit PECK = GALLON.times(2);
+ public static final LinearUnit BUSHEL = PECK.times(4);
+
+ public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3);
+ public static final LinearUnit CUBIC_FOOT = Length.FOOT.toExponent(3);
+ public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3);
+ public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT);
+ }
+
+ public static final LinearUnit OUNCE_FORCE = Mass.OUNCE.times(SI.Constants.EARTH_GRAVITY);
+ public static final LinearUnit POUND_FORCE = Mass.POUND.times(SI.Constants.EARTH_GRAVITY);
+
+ public static final LinearUnit BRITISH_THERMAL_UNIT = SI.JOULE.times(1055.06);
+ public static final LinearUnit CALORIE = SI.JOULE.times(4.184);
+ public static final LinearUnit KILOCALORIE = SI.JOULE.times(4184);
+
+ public static final Unit FAHRENHEIT = Unit.fromConversionFunctions(SI.KELVIN.getBase(),
+ tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8);
+}
diff --git a/src/main/java/org/unitConverter/unit/FunctionalUnit.java b/src/main/java/org/unitConverter/unit/FunctionalUnit.java
new file mode 100644
index 0000000..586e0d7
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/FunctionalUnit.java
@@ -0,0 +1,109 @@
+/**
+ * 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;
+import java.util.function.DoubleUnaryOperator;
+
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * A unit that uses functional objects to convert to and from its base.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-05-22
+ */
+final class FunctionalUnit extends Unit {
+ /**
+ * A function that accepts a value expressed in the unit's base and returns that value expressed in this unit.
+ *
+ * @since 2019-05-22
+ */
+ private final DoubleUnaryOperator converterFrom;
+
+ /**
+ * A function that accepts a value expressed in the unit and returns that value expressed in the unit's base.
+ *
+ * @since 2019-05-22
+ */
+ private final DoubleUnaryOperator converterTo;
+
+ /**
+ * Creates the {@code FunctionalUnit}.
+ *
+ * @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.
+ * @throws NullPointerException
+ * if any argument is null
+ * @since 2019-05-22
+ */
+ public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom,
+ final DoubleUnaryOperator converterTo) {
+ super(base, NameSymbol.EMPTY);
+ this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null.");
+ this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null.");
+ }
+
+ /**
+ * Creates the {@code FunctionalUnit}.
+ *
+ * @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.
+ * @throws NullPointerException
+ * if any argument is null
+ * @since 2019-05-22
+ */
+ public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom,
+ final DoubleUnaryOperator converterTo, final NameSymbol ns) {
+ super(base, ns);
+ this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null.");
+ this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null.");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Uses {@code converterFrom} to convert.
+ */
+ @Override
+ public double convertFromBase(final double value) {
+ return this.converterFrom.applyAsDouble(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Uses {@code converterTo} to convert.
+ */
+ @Override
+ public double convertToBase(final double value) {
+ return this.converterTo.applyAsDouble(value);
+ }
+
+}
diff --git a/src/main/java/org/unitConverter/unit/FunctionalUnitlike.java b/src/main/java/org/unitConverter/unit/FunctionalUnitlike.java
new file mode 100644
index 0000000..21c1fca
--- /dev/null
+++ b/src/main/java/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/main/java/org/unitConverter/unit/LinearUnit.java b/src/main/java/org/unitConverter/unit/LinearUnit.java
new file mode 100644
index 0000000..b7f33d5
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/LinearUnit.java
@@ -0,0 +1,441 @@
+/**
+ * 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;
+
+import org.unitConverter.math.DecimalComparison;
+import org.unitConverter.math.ObjectProduct;
+import org.unitConverter.math.UncertainDouble;
+
+/**
+ * A unit that can be expressed as a product of its base and a number. For
+ * example, kilometres, inches and pounds.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class LinearUnit extends Unit {
+ /**
+ * Gets a {@code LinearUnit} from a unit and a value. For example, converts
+ * '59 °F' to a linear unit with the value of '288.15 K'
+ *
+ * @param unit unit to convert
+ * @param value value to convert
+ * @return value expressed as a {@code LinearUnit}
+ * @since 2019-10-16
+ * @throws NullPointerException if unit is null
+ */
+ public static LinearUnit fromUnitValue(final Unit unit, final double value) {
+ return new LinearUnit(
+ Objects.requireNonNull(unit, "unit must not be null.").getBase(),
+ unit.convertToBase(value), NameSymbol.EMPTY);
+ }
+
+ /**
+ * Gets a {@code LinearUnit} from a unit and a value. For example, converts
+ * '59 °F' to a linear unit with the value of '288.15 K'
+ *
+ * @param unit unit to convert
+ * @param value value to convert
+ * @param ns name(s) and symbol of unit
+ * @return value expressed as a {@code LinearUnit}
+ * @since 2019-10-21
+ * @throws NullPointerException if unit or ns is null
+ */
+ public static LinearUnit fromUnitValue(final Unit unit, final double value,
+ final NameSymbol ns) {
+ return new LinearUnit(
+ Objects.requireNonNull(unit, "unit must not be null.").getBase(),
+ unit.convertToBase(value), ns);
+ }
+
+ /**
+ * @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}.
+ *
+ * @param unitBase unit base to multiply by
+ * @param conversionFactor number to multiply base by
+ * @return product of base and conversion factor
+ * @since 2019-10-16
+ * @throws NullPointerException if unitBase is null
+ */
+ public static LinearUnit valueOf(final ObjectProduct<BaseUnit> unitBase,
+ final double conversionFactor) {
+ return new LinearUnit(unitBase, conversionFactor, 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}.
+ *
+ * @param unitBase unit base to multiply by
+ * @param conversionFactor number to multiply base by
+ * @param ns name(s) and symbol of unit
+ * @return product of base and conversion factor
+ * @since 2019-10-21
+ * @throws NullPointerException if unitBase is null
+ */
+ public static LinearUnit valueOf(final ObjectProduct<BaseUnit> unitBase,
+ final double conversionFactor, final NameSymbol ns) {
+ return new LinearUnit(unitBase, conversionFactor, ns);
+ }
+
+ /**
+ * The value of this unit as represented in its base form. Mathematically,
+ *
+ * <pre>
+ * this = conversionFactor * getBase()
+ * </pre>
+ *
+ * @since 2019-10-16
+ */
+ private final double conversionFactor;
+
+ /**
+ * Creates the {@code LinearUnit}.
+ *
+ * @param unitBase base of linear unit
+ * @param conversionFactor conversion factor between base and unit
+ * @since 2019-10-16
+ */
+ private LinearUnit(final ObjectProduct<BaseUnit> unitBase,
+ final double conversionFactor, final NameSymbol ns) {
+ super(unitBase, ns);
+ this.conversionFactor = conversionFactor;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Converts by dividing by {@code conversionFactor}
+ */
+ @Override
+ protected double convertFromBase(final double value) {
+ return value / this.getConversionFactor();
+ }
+
+ /**
+ * Converts an {@code UncertainDouble} value expressed in this unit to an
+ * {@code UncertainValue} value expressed in {@code other}.
+ *
+ * @param other unit to convert to
+ * @param value value to convert
+ * @return converted value
+ * @since 2019-09-07
+ * @throws IllegalArgumentException if {@code other} is incompatible for
+ * conversion with this unit (as tested by
+ * {@link Unit#canConvertTo}).
+ * @throws NullPointerException if value or other is null
+ */
+ public UncertainDouble convertTo(LinearUnit other, UncertainDouble value) {
+ Objects.requireNonNull(other, "other must not be null.");
+ Objects.requireNonNull(value, "value may not be null.");
+ if (this.canConvertTo(other))
+ return value.timesExact(
+ this.getConversionFactor() / other.getConversionFactor());
+ else
+ throw new IllegalArgumentException(
+ String.format("Cannot convert from %s to %s.", this, other));
+
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Converts by multiplying by {@code conversionFactor}
+ */
+ @Override
+ protected double convertToBase(final double value) {
+ return value * this.getConversionFactor();
+ }
+
+ /**
+ * Converts an {@code UncertainDouble} to the base unit.
+ *
+ * @since 2020-09-07
+ */
+ UncertainDouble convertToBase(final UncertainDouble value) {
+ return value.timesExact(this.getConversionFactor());
+ }
+
+ /**
+ * Divides this unit by a scalar.
+ *
+ * @param divisor scalar to divide by
+ * @return quotient
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final double divisor) {
+ return valueOf(this.getBase(), this.getConversionFactor() / divisor);
+ }
+
+ /**
+ * Returns the quotient of this unit and another.
+ *
+ * @param divisor unit to divide by
+ * @return quotient of two units
+ * @throws NullPointerException if {@code divisor} is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final LinearUnit divisor) {
+ Objects.requireNonNull(divisor, "other must not be null");
+
+ // divide the units
+ final ObjectProduct<BaseUnit> base = this.getBase()
+ .dividedBy(divisor.getBase());
+ return valueOf(base,
+ this.getConversionFactor() / divisor.getConversionFactor());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Uses the base and conversion factor of units to test for equality.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof LinearUnit))
+ return false;
+ final LinearUnit other = (LinearUnit) obj;
+ return Objects.equals(this.getBase(), other.getBase())
+ && DecimalComparison.equals(this.getConversionFactor(),
+ other.getConversionFactor());
+ }
+
+ /**
+ * @return conversion factor
+ * @since 2019-10-16
+ */
+ public double getConversionFactor() {
+ return this.conversionFactor;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Uses the base and conversion factor to compute a hash code.
+ */
+ @Override
+ public int hashCode() {
+ return 31 * this.getBase().hashCode()
+ + DecimalComparison.hash(this.getConversionFactor());
+ }
+
+ /**
+ * @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there
+ * is a {@code BaseUnit b} where
+ * {@code b.asLinearUnit().equals(this)} returns {@code true}.)
+ * @since 2019-10-16
+ */
+ public boolean isBase() {
+ return this.isCoherent() && this.getBase().isSingleObject();
+ }
+
+ /**
+ * @return whether this unit is coherent (i.e. has conversion factor 1)
+ * @since 2019-10-16
+ */
+ public boolean isCoherent() {
+ return this.getConversionFactor() == 1;
+ }
+
+ /**
+ * Returns the difference of this unit and another.
+ * <p>
+ * Two units can be subtracted if they have the same base. Note that
+ * {@link #canConvertTo} can be used to determine this. If {@code subtrahend}
+ * does not meet this condition, an {@code IllegalArgumentException} will be
+ * thrown.
+ * </p>
+ *
+ * @param subtrahend unit to subtract
+ * @return difference of units
+ * @throws IllegalArgumentException if {@code subtrahend} is not compatible
+ * for subtraction as described above
+ * @throws NullPointerException if {@code subtrahend} is null
+ * @since 2019-03-17
+ * @since v0.2.0
+ */
+ public LinearUnit minus(final LinearUnit subtrahend) {
+ Objects.requireNonNull(subtrahend, "addend must not be null.");
+
+ // reject subtrahends that cannot be added to this unit
+ if (!this.getBase().equals(subtrahend.getBase()))
+ throw new IllegalArgumentException(String.format(
+ "Incompatible units for subtraction \"%s\" and \"%s\".", this,
+ subtrahend));
+
+ // subtract the units
+ return valueOf(this.getBase(),
+ this.getConversionFactor() - subtrahend.getConversionFactor());
+ }
+
+ /**
+ * Returns the sum of this unit and another.
+ * <p>
+ * Two units can be added if they have the same base. Note that
+ * {@link #canConvertTo} can be used to determine this. If {@code addend}
+ * does not meet this condition, an {@code IllegalArgumentException} will be
+ * thrown.
+ * </p>
+ *
+ * @param addend unit to add
+ * @return sum of units
+ * @throws IllegalArgumentException if {@code addend} is not compatible for
+ * addition as described above
+ * @throws NullPointerException if {@code addend} is null
+ * @since 2019-03-17
+ * @since v0.2.0
+ */
+ public LinearUnit plus(final LinearUnit addend) {
+ Objects.requireNonNull(addend, "addend must not be null.");
+
+ // reject addends that cannot be added to this unit
+ if (!this.getBase().equals(addend.getBase()))
+ throw new IllegalArgumentException(String.format(
+ "Incompatible units for addition \"%s\" and \"%s\".", this,
+ addend));
+
+ // add the units
+ return valueOf(this.getBase(),
+ this.getConversionFactor() + addend.getConversionFactor());
+ }
+
+ /**
+ * Multiplies this unit by a scalar.
+ *
+ * @param multiplier scalar to multiply by
+ * @return product
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit times(final double multiplier) {
+ return valueOf(this.getBase(), this.getConversionFactor() * multiplier);
+ }
+
+ /**
+ * Returns the product of this unit and another.
+ *
+ * @param multiplier unit to multiply by
+ * @return product of two units
+ * @throws NullPointerException if {@code multiplier} is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public LinearUnit times(final LinearUnit multiplier) {
+ Objects.requireNonNull(multiplier, "other must not be null");
+
+ // multiply the units
+ final ObjectProduct<BaseUnit> base = this.getBase()
+ .times(multiplier.getBase());
+ return valueOf(base,
+ this.getConversionFactor() * multiplier.getConversionFactor());
+ }
+
+ /**
+ * Returns this unit but to an exponent.
+ *
+ * @param exponent exponent to exponentiate unit to
+ * @return exponentiated unit
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public LinearUnit toExponent(final int exponent) {
+ return valueOf(this.getBase().toExponent(exponent),
+ Math.pow(this.conversionFactor, exponent));
+ }
+
+ /**
+ * @return a string providing a definition of this unit
+ * @since 2019-10-21
+ */
+ @Override
+ public String toString() {
+ return this.getPrimaryName().orElse("Unnamed unit")
+ + (this.getSymbol().isPresent()
+ ? String.format(" (%s)", this.getSymbol().get())
+ : "")
+ + ", " + Double.toString(this.conversionFactor) + " * "
+ + this.getBase().toString(u -> u.getSymbol().get());
+ }
+
+ @Override
+ public LinearUnit withName(final NameSymbol ns) {
+ return valueOf(this.getBase(), this.getConversionFactor(), ns);
+ }
+
+ /**
+ * Returns the result of applying {@code prefix} to this unit.
+ * <p>
+ * If this unit and the provided prefix have a primary name, the returned
+ * unit will have a primary name (prefix's name + unit's name). <br>
+ * If this unit and the provided prefix have a symbol, the returned unit will
+ * have a symbol. <br>
+ * This method ignores alternate names of both this unit and the provided
+ * prefix.
+ *
+ * @param prefix prefix to apply
+ * @return unit with prefix
+ * @since 2019-03-18
+ * @since v0.2.0
+ * @throws NullPointerException if prefix is null
+ */
+ public LinearUnit withPrefix(final UnitPrefix prefix) {
+ final LinearUnit unit = this.times(prefix.getMultiplier());
+
+ // create new name and symbol, if possible
+ final String name;
+ if (this.getPrimaryName().isPresent()
+ && prefix.getPrimaryName().isPresent()) {
+ name = prefix.getPrimaryName().get() + this.getPrimaryName().get();
+ } else {
+ name = null;
+ }
+
+ final String symbol;
+ if (this.getSymbol().isPresent() && prefix.getSymbol().isPresent()) {
+ symbol = prefix.getSymbol().get() + this.getSymbol().get();
+ } else {
+ symbol = null;
+ }
+
+ return unit.withName(NameSymbol.ofNullable(name, symbol));
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/LinearUnitValue.java b/src/main/java/org/unitConverter/unit/LinearUnitValue.java
new file mode 100644
index 0000000..8de734e
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/LinearUnitValue.java
@@ -0,0 +1,341 @@
+/**
+ * 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;
+import java.util.Optional;
+
+import org.unitConverter.math.DecimalComparison;
+import org.unitConverter.math.UncertainDouble;
+
+/**
+ * A possibly uncertain value expressed in a linear unit.
+ *
+ * Unless otherwise indicated, all methods in this class throw a
+ * {@code NullPointerException} when an argument is null.
+ *
+ * @author Adrien Hopkins
+ * @since 2020-07-26
+ */
+public final class LinearUnitValue {
+ public static final LinearUnitValue ONE = getExact(SI.ONE, 1);
+
+ /**
+ * Gets an exact {@code LinearUnitValue}
+ *
+ * @param unit unit to express with
+ * @param value value to express
+ * @return exact {@code LinearUnitValue} instance
+ * @since 2020-07-26
+ */
+ public static final LinearUnitValue getExact(final LinearUnit unit,
+ final double value) {
+ return new LinearUnitValue(
+ Objects.requireNonNull(unit, "unit must not be null"),
+ UncertainDouble.of(value, 0));
+ }
+
+ /**
+ * Gets an uncertain {@code LinearUnitValue}
+ *
+ * @param unit unit to express with
+ * @param value value to express
+ * @param uncertainty absolute uncertainty of value
+ * @return uncertain {@code LinearUnitValue} instance
+ * @since 2020-07-26
+ */
+ public static final LinearUnitValue of(final LinearUnit unit,
+ final UncertainDouble value) {
+ return new LinearUnitValue(
+ Objects.requireNonNull(unit, "unit must not be null"),
+ Objects.requireNonNull(value, "value may not be null"));
+ }
+
+ private final LinearUnit unit;
+
+ private final UncertainDouble value;
+
+ /**
+ * @param unit unit to express as
+ * @param value value to express
+ * @since 2020-07-26
+ */
+ private LinearUnitValue(final LinearUnit unit, final UncertainDouble value) {
+ this.unit = unit;
+ this.value = value;
+ }
+
+ /**
+ * @return this value as a {@code UnitValue}. All uncertainty information is
+ * removed from the returned value.
+ * @since 2020-08-04
+ */
+ public final UnitValue asUnitValue() {
+ return UnitValue.of(this.unit, this.value.value());
+ }
+
+ /**
+ * @param other a {@code LinearUnit}
+ * @return true iff this value can be represented with {@code other}.
+ * @since 2020-07-26
+ */
+ public final boolean canConvertTo(final LinearUnit other) {
+ return this.unit.canConvertTo(other);
+ }
+
+ /**
+ * Returns a LinearUnitValue that represents the same value expressed in a
+ * different unit
+ *
+ * @param other new unit to express value in
+ * @return value expressed in {@code other}
+ * @since 2020-07-26
+ */
+ public final LinearUnitValue convertTo(final LinearUnit other) {
+ return LinearUnitValue.of(other, this.unit.convertTo(other, this.value));
+ }
+
+ /**
+ * Divides this value by a scalar
+ *
+ * @param divisor value to divide by
+ * @return multiplied value
+ * @since 2020-07-28
+ */
+ public LinearUnitValue dividedBy(final double divisor) {
+ return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor));
+ }
+
+ /**
+ * Divides this value by another value
+ *
+ * @param divisor value to multiply by
+ * @return quotient
+ * @since 2020-07-28
+ */
+ public LinearUnitValue dividedBy(final LinearUnitValue divisor) {
+ return LinearUnitValue.of(this.unit.dividedBy(divisor.unit),
+ this.value.dividedBy(divisor.value));
+ }
+
+ /**
+ * 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.
+ *
+ * @since 2020-07-26
+ * @see #equals(Object, boolean)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof LinearUnitValue))
+ return false;
+ final LinearUnitValue other = (LinearUnitValue) obj;
+ return Objects.equals(this.unit.getBase(), other.unit.getBase())
+ && this.unit.convertToBase(this.value)
+ .equals(other.unit.convertToBase(other.value));
+ }
+
+ /**
+ * 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.
+ * <p>
+ * If avoidFPErrors is true, this method will attempt to avoid floating-point
+ * errors, at the cost of not always being transitive.
+ *
+ * @since 2020-07-28
+ */
+ public boolean equals(final Object obj, final boolean avoidFPErrors) {
+ if (!avoidFPErrors)
+ return this.equals(obj);
+ if (!(obj instanceof LinearUnitValue))
+ return false;
+ final LinearUnitValue other = (LinearUnitValue) obj;
+ return Objects.equals(this.unit.getBase(), other.unit.getBase())
+ && DecimalComparison.equals(this.unit.convertToBase(this.value),
+ other.unit.convertToBase(other.value));
+ }
+
+ /**
+ * @param other another {@code LinearUnitValue}
+ * @return true iff this and other are within each other's uncertainty range
+ *
+ * @since 2020-07-26
+ */
+ public boolean equivalent(final LinearUnitValue other) {
+ if (other == null
+ || !Objects.equals(this.unit.getBase(), other.unit.getBase()))
+ return false;
+ final LinearUnit base = LinearUnit.valueOf(this.unit.getBase(), 1);
+ final LinearUnitValue thisBase = this.convertTo(base);
+ final LinearUnitValue otherBase = other.convertTo(base);
+
+ return thisBase.value.equivalent(otherBase.value);
+ }
+
+ /**
+ * @return the unit
+ * @since 2020-09-29
+ */
+ public final LinearUnit getUnit() {
+ return this.unit;
+ }
+
+ /**
+ * @return the value
+ * @since 2020-09-29
+ */
+ public final UncertainDouble getValue() {
+ return this.value;
+ }
+
+ /**
+ * @return the exact value
+ * @since 2020-09-07
+ */
+ public final double getValueExact() {
+ return this.value.value();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.unit.getBase(),
+ this.unit.convertToBase(this.getValue()));
+ }
+
+ /**
+ * Returns the difference of this value and another, expressed in this
+ * value's unit
+ *
+ * @param subtrahend value to subtract
+ * @return difference of values
+ * @throws IllegalArgumentException if {@code subtrahend} has a unit that is
+ * not compatible for addition
+ * @since 2020-07-26
+ */
+ public LinearUnitValue minus(final LinearUnitValue subtrahend) {
+ Objects.requireNonNull(subtrahend, "subtrahend may not be null");
+
+ if (!this.canConvertTo(subtrahend.unit))
+ throw new IllegalArgumentException(String.format(
+ "Incompatible units for subtraction \"%s\" and \"%s\".",
+ this.unit, subtrahend.unit));
+
+ final LinearUnitValue otherConverted = subtrahend.convertTo(this.unit);
+ return LinearUnitValue.of(this.unit,
+ this.value.minus(otherConverted.value));
+ }
+
+ /**
+ * Returns the sum of this value and another, expressed in this value's unit
+ *
+ * @param addend value to add
+ * @return sum of values
+ * @throws IllegalArgumentException if {@code addend} has a unit that is not
+ * compatible for addition
+ * @since 2020-07-26
+ */
+ public LinearUnitValue plus(final LinearUnitValue addend) {
+ Objects.requireNonNull(addend, "addend may not be null");
+
+ if (!this.canConvertTo(addend.unit))
+ throw new IllegalArgumentException(String.format(
+ "Incompatible units for addition \"%s\" and \"%s\".", this.unit,
+ addend.unit));
+
+ final LinearUnitValue otherConverted = addend.convertTo(this.unit);
+ return LinearUnitValue.of(this.unit,
+ this.value.plus(otherConverted.value));
+ }
+
+ /**
+ * Multiplies this value by a scalar
+ *
+ * @param multiplier value to multiply by
+ * @return multiplied value
+ * @since 2020-07-28
+ */
+ public LinearUnitValue times(final double multiplier) {
+ return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier));
+ }
+
+ /**
+ * Multiplies this value by another value
+ *
+ * @param multiplier value to multiply by
+ * @return product
+ * @since 2020-07-28
+ */
+ public LinearUnitValue times(final LinearUnitValue multiplier) {
+ return LinearUnitValue.of(this.unit.times(multiplier.unit),
+ this.value.times(multiplier.value));
+ }
+
+ /**
+ * Raises a value to an exponent
+ *
+ * @param exponent exponent to raise to
+ * @return result of exponentiation
+ * @since 2020-07-28
+ */
+ public LinearUnitValue toExponent(final int exponent) {
+ return LinearUnitValue.of(this.unit.toExponent(exponent),
+ this.value.toExponentExact(exponent));
+ }
+
+ @Override
+ public String toString() {
+ return this.toString(!this.value.isExact());
+ }
+
+ /**
+ * Returns a string representing the object. <br>
+ * If the attached unit has a name or symbol, the string looks like "12 km".
+ * Otherwise, it looks like "13 unnamed unit (= 2 m/s)".
+ * <p>
+ * If showUncertainty is true, strings like "35 ± 8" are shown instead of
+ * single numbers.
+ * <p>
+ * Non-exact values are rounded intelligently based on their uncertainty.
+ *
+ * @since 2020-07-26
+ */
+ public String toString(final boolean showUncertainty) {
+ final Optional<String> primaryName = this.unit.getPrimaryName();
+ final Optional<String> symbol = this.unit.getSymbol();
+ final String chosenName = symbol.orElse(primaryName.orElse(null));
+
+ final UncertainDouble baseValue = this.unit.convertToBase(this.value);
+
+ // get rounded strings
+ // if showUncertainty is true, add brackets around the string
+ final String valueString = showUncertainty ? "("
+ : "" + this.value.toString(showUncertainty)
+ + (showUncertainty ? ")" : "");
+ final String baseValueString = showUncertainty ? "("
+ : "" + baseValue.toString(showUncertainty)
+ + (showUncertainty ? ")" : "");
+
+ // create string
+ if (primaryName.isEmpty() && symbol.isEmpty())
+ return String.format("%s unnamed unit (= %s %s)", valueString,
+ baseValueString, this.unit.getBase());
+ else
+ return String.format("%s %s", valueString, chosenName);
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/MultiUnit.java b/src/main/java/org/unitConverter/unit/MultiUnit.java
new file mode 100644
index 0000000..a1623f8
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/MultiUnit.java
@@ -0,0 +1,160 @@
+/**
+ * Copyright (C) 2020 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package org.unitConverter.unit;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * A combination of units, like "5 foot + 7 inch". All but the last units should
+ * have a whole number value associated with them.
+ *
+ * @since 2020-10-02
+ */
+public final class MultiUnit extends Unitlike<List<Double>> {
+ /**
+ * Creates a {@code MultiUnit} from its units. It will not have a name or
+ * symbol.
+ *
+ * @since 2020-10-03
+ */
+ public static final MultiUnit of(LinearUnit... units) {
+ return of(Arrays.asList(units));
+ }
+
+ /**
+ * Creates a {@code MultiUnit} from its units. It will not have a name or
+ * symbol.
+ *
+ * @since 2020-10-03
+ */
+ public static final MultiUnit of(List<LinearUnit> units) {
+ if (units.size() < 1)
+ throw new IllegalArgumentException("Must have at least one unit");
+ final ObjectProduct<BaseUnit> unitBase = units.get(0).getBase();
+ for (final LinearUnit unit : units) {
+ if (!unitBase.equals(unit.getBase()))
+ throw new IllegalArgumentException(
+ "All units must have the same base.");
+ }
+ return new MultiUnit(new ArrayList<>(units), unitBase, NameSymbol.EMPTY);
+ }
+
+ /**
+ * The units that make up this value.
+ */
+ private final List<LinearUnit> units;
+
+ /**
+ * Creates a {@code MultiUnit}.
+ *
+ * @since 2020-10-03
+ */
+ private MultiUnit(List<LinearUnit> units, ObjectProduct<BaseUnit> unitBase,
+ NameSymbol ns) {
+ super(unitBase, ns);
+ this.units = units;
+ }
+
+ @Override
+ protected List<Double> convertFromBase(double value) {
+ final List<Double> values = new ArrayList<>(this.units.size());
+ double temp = value;
+
+ for (final LinearUnit unit : this.units.subList(0,
+ this.units.size() - 1)) {
+ values.add(Math.floor(temp / unit.getConversionFactor()));
+ temp %= unit.getConversionFactor();
+ }
+
+ values.add(this.units.size() - 1,
+ this.units.get(this.units.size() - 1).convertFromBase(temp));
+
+ return values;
+ }
+
+ /**
+ * Converts a value expressed in this unitlike form to a value expressed in
+ * {@code other}.
+ *
+ * @implSpec If conversion is possible, this implementation returns
+ * {@code other.convertFromBase(this.convertToBase(value))}.
+ * Therefore, overriding either of those methods will change the
+ * output of this method.
+ *
+ * @param other unit to convert to
+ * @param value value to convert
+ * @return converted value
+ * @since 2020-10-03
+ * @throws IllegalArgumentException if {@code other} is incompatible for
+ * conversion with this unitlike form (as
+ * tested by {@link Unit#canConvertTo}).
+ * @throws NullPointerException if other is null
+ */
+ public final <U extends Unitlike<V>, V> V convertTo(U other,
+ double... values) {
+ final List<Double> valueList = new ArrayList<>(values.length);
+ for (final double d : values) {
+ valueList.add(d);
+ }
+
+ return this.convertTo(other, valueList);
+ }
+
+ /**
+ * Converts a value expressed in this unitlike form to a value expressed in
+ * {@code other}.
+ *
+ * @implSpec If conversion is possible, this implementation returns
+ * {@code other.convertFromBase(this.convertToBase(value))}.
+ * Therefore, overriding either of those methods will change the
+ * output of this method.
+ *
+ * @param other unit to convert to
+ * @param value value to convert
+ * @return converted value
+ * @since 2020-10-03
+ * @throws IllegalArgumentException if {@code other} is incompatible for
+ * conversion with this unitlike form (as
+ * tested by {@link Unit#canConvertTo}).
+ * @throws NullPointerException if other is null
+ */
+ public final double convertTo(Unit other, double... values) {
+ final List<Double> valueList = new ArrayList<>(values.length);
+ for (final double d : values) {
+ valueList.add(d);
+ }
+
+ return this.convertTo(other, valueList);
+ }
+
+ @Override
+ protected double convertToBase(List<Double> value) {
+ if (value.size() != this.units.size())
+ throw new IllegalArgumentException("Wrong number of values for "
+ + this.units.size() + "-unit MultiUnit.");
+
+ double baseValue = 0;
+ for (int i = 0; i < this.units.size(); i++) {
+ baseValue += value.get(i) * this.units.get(i).getConversionFactor();
+ }
+ return baseValue;
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/NameSymbol.java b/src/main/java/org/unitConverter/unit/NameSymbol.java
new file mode 100644
index 0000000..8d8302a
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/NameSymbol.java
@@ -0,0 +1,280 @@
+/**
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * A class that can be used to specify names and a symbol for a unit.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-21
+ */
+public final class NameSymbol {
+ public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(),
+ Optional.empty(), new HashSet<>());
+
+ /**
+ * Creates a {@code NameSymbol}, ensuring that if primaryName is null and
+ * otherNames is not empty, one name is moved from otherNames to primaryName
+ *
+ * Ensure that otherNames is a copy of the inputted argument.
+ */
+ private static final NameSymbol create(final String name,
+ final String symbol, final Set<String> otherNames) {
+ final Optional<String> primaryName;
+
+ if (name == null && !otherNames.isEmpty()) {
+ // get primary name and remove it from savedNames
+ final Iterator<String> it = otherNames.iterator();
+ assert it.hasNext();
+ primaryName = Optional.of(it.next());
+ otherNames.remove(primaryName.get());
+ } else {
+ primaryName = Optional.ofNullable(name);
+ }
+
+ return new NameSymbol(primaryName, Optional.ofNullable(symbol),
+ otherNames);
+ }
+
+ /**
+ * Gets a {@code NameSymbol} with a primary name, a symbol and no other
+ * names.
+ *
+ * @param name name to use
+ * @param symbol symbol to use
+ * @return NameSymbol instance
+ * @since 2019-10-21
+ * @throws NullPointerException if name or symbol is null
+ */
+ public static final NameSymbol of(final String name, final String symbol) {
+ return new NameSymbol(Optional.of(name), Optional.of(symbol),
+ new HashSet<>());
+ }
+
+ /**
+ * Gets a {@code NameSymbol} with a primary name, a symbol and additional
+ * names.
+ *
+ * @param name name to use
+ * @param symbol symbol to use
+ * @param otherNames other names to use
+ * @return NameSymbol instance
+ * @since 2019-10-21
+ * @throws NullPointerException if any argument is null
+ */
+ public static final NameSymbol of(final String name, final String symbol,
+ final Set<String> otherNames) {
+ return new NameSymbol(Optional.of(name), Optional.of(symbol),
+ new HashSet<>(Objects.requireNonNull(otherNames,
+ "otherNames must not be null.")));
+ }
+
+ /**
+ * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional
+ * names.
+ *
+ * @param name name to use
+ * @param symbol symbol to use
+ * @param otherNames other names to use
+ * @return NameSymbol instance
+ * @since 2019-10-21
+ * @throws NullPointerException if any argument is null
+ */
+ public static final NameSymbol of(final String name, final String symbol,
+ final String... otherNames) {
+ return new NameSymbol(Optional.of(name), Optional.of(symbol),
+ new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames,
+ "otherNames must not be null."))));
+ }
+
+ /**
+ * Gets a {@code NameSymbol} with a primary name, no symbol, and no other
+ * names.
+ *
+ * @param name name to use
+ * @return NameSymbol instance
+ * @since 2019-10-21
+ * @throws NullPointerException if name is null
+ */
+ public static final NameSymbol ofName(final String name) {
+ return new NameSymbol(Optional.of(name), Optional.empty(),
+ new HashSet<>());
+ }
+
+ /**
+ * Gets a {@code NameSymbol} with a primary name, a symbol and additional
+ * names.
+ * <p>
+ * If any argument is null, this static factory replaces it with an empty
+ * Optional or empty Set.
+ * <p>
+ * If {@code name} is null and {@code otherNames} is not empty, a primary
+ * name will be picked from {@code otherNames}. This name will not appear in
+ * getOtherNames().
+ *
+ * @param name name to use
+ * @param symbol symbol to use
+ * @param otherNames other names to use
+ * @return NameSymbol instance
+ * @since 2019-11-26
+ */
+ public static final NameSymbol ofNullable(final String name,
+ final String symbol, final Set<String> otherNames) {
+ return NameSymbol.create(name, symbol,
+ otherNames == null ? new HashSet<>() : new HashSet<>(otherNames));
+ }
+
+ /**
+ * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional
+ * names.
+ * <p>
+ * If any argument is null, this static factory replaces it with an empty
+ * Optional or empty Set.
+ * <p>
+ * If {@code name} is null and {@code otherNames} is not empty, a primary
+ * name will be picked from {@code otherNames}. This name will not appear in
+ * getOtherNames().
+ *
+ * @param name name to use
+ * @param symbol symbol to use
+ * @param otherNames other names to use
+ * @return NameSymbol instance
+ * @since 2019-11-26
+ */
+ public static final NameSymbol ofNullable(final String name,
+ final String symbol, final String... otherNames) {
+ return create(name, symbol, otherNames == null ? new HashSet<>()
+ : new HashSet<>(Arrays.asList(otherNames)));
+ }
+
+ /**
+ * Gets a {@code NameSymbol} with a symbol and no names.
+ *
+ * @param symbol symbol to use
+ * @return NameSymbol instance
+ * @since 2019-10-21
+ * @throws NullPointerException if symbol is null
+ */
+ public static final NameSymbol ofSymbol(final String symbol) {
+ return new NameSymbol(Optional.empty(), Optional.of(symbol),
+ new HashSet<>());
+ }
+
+ private final Optional<String> primaryName;
+ private final Optional<String> symbol;
+
+ private final Set<String> otherNames;
+
+ /**
+ * Creates the {@code NameSymbol}.
+ *
+ * @param primaryName primary name of unit
+ * @param symbol symbol used to represent unit
+ * @param otherNames other names and/or spellings, should be a mutable copy
+ * of the argument
+ * @since 2019-10-21
+ */
+ private NameSymbol(final Optional<String> primaryName,
+ final Optional<String> symbol, final Set<String> otherNames) {
+ this.primaryName = primaryName;
+ this.symbol = symbol;
+ otherNames.remove(null);
+ this.otherNames = Collections.unmodifiableSet(otherNames);
+
+ if (this.primaryName.isEmpty()) {
+ assert this.otherNames.isEmpty();
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof NameSymbol))
+ return false;
+ final NameSymbol other = (NameSymbol) obj;
+ if (this.otherNames == null) {
+ if (other.otherNames != null)
+ return false;
+ } else if (!this.otherNames.equals(other.otherNames))
+ return false;
+ if (this.primaryName == null) {
+ if (other.primaryName != null)
+ return false;
+ } else if (!this.primaryName.equals(other.primaryName))
+ return false;
+ if (this.symbol == null) {
+ if (other.symbol != null)
+ return false;
+ } else if (!this.symbol.equals(other.symbol))
+ return false;
+ return true;
+ }
+
+ /**
+ * @return otherNames
+ * @since 2019-10-21
+ */
+ public final Set<String> getOtherNames() {
+ return this.otherNames;
+ }
+
+ /**
+ * @return primaryName
+ * @since 2019-10-21
+ */
+ public final Optional<String> getPrimaryName() {
+ return this.primaryName;
+ }
+
+ /**
+ * @return symbol
+ * @since 2019-10-21
+ */
+ public final Optional<String> getSymbol() {
+ return this.symbol;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + (this.otherNames == null ? 0 : this.otherNames.hashCode());
+ result = prime * result
+ + (this.primaryName == null ? 0 : this.primaryName.hashCode());
+ result = prime * result
+ + (this.symbol == null ? 0 : this.symbol.hashCode());
+ return result;
+ }
+
+ /**
+ * @return true iff this {@code NameSymbol} contains no names or symbols.
+ */
+ public final boolean isEmpty() {
+ // if primaryName is empty, otherNames must also be empty
+ return this.primaryName.isEmpty() && this.symbol.isEmpty();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/unitConverter/unit/Nameable.java b/src/main/java/org/unitConverter/unit/Nameable.java
new file mode 100644
index 0000000..36740ab
--- /dev/null
+++ b/src/main/java/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/main/java/org/unitConverter/unit/SI.java b/src/main/java/org/unitConverter/unit/SI.java
new file mode 100644
index 0000000..81736f3
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/SI.java
@@ -0,0 +1,479 @@
+/**
+ * Copyright (C) 2018 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.Set;
+
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * All of the units, prefixes and dimensions that are used by the SI, as well as
+ * some outside the SI.
+ *
+ * <p>
+ * This class does not include prefixed units. To obtain prefixed units, use
+ * {@link LinearUnit#withPrefix}:
+ *
+ * <pre>
+ * LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
+ * </pre>
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class SI {
+ /// dimensions used by SI units
+ // base dimensions, as BaseDimensions
+ public static final class BaseDimensions {
+ public static final BaseDimension LENGTH = BaseDimension.valueOf("Length",
+ "L");
+ public static final BaseDimension MASS = BaseDimension.valueOf("Mass",
+ "M");
+ public static final BaseDimension TIME = BaseDimension.valueOf("Time",
+ "T");
+ public static final BaseDimension ELECTRIC_CURRENT = BaseDimension
+ .valueOf("Electric Current", "I");
+ public static final BaseDimension TEMPERATURE = BaseDimension
+ .valueOf("Temperature", "\u0398"); // theta symbol
+ public static final BaseDimension QUANTITY = BaseDimension
+ .valueOf("Quantity", "N");
+ public static final BaseDimension LUMINOUS_INTENSITY = BaseDimension
+ .valueOf("Luminous Intensity", "J");
+ public static final BaseDimension INFORMATION = BaseDimension
+ .valueOf("Information", "Info"); // non-SI
+ public static final BaseDimension CURRENCY = BaseDimension
+ .valueOf("Currency", "$$"); // non-SI
+
+ // You may NOT get SI.BaseDimensions instances!
+ private BaseDimensions() {
+ throw new AssertionError();
+ }
+ }
+
+ /// base units of the SI
+ // suppressing warnings since these are the same object, but in a different
+ /// form (class)
+ @SuppressWarnings("hiding")
+ public static final class BaseUnits {
+ public static final BaseUnit METRE = BaseUnit
+ .valueOf(BaseDimensions.LENGTH, "metre", "m");
+ public static final BaseUnit KILOGRAM = BaseUnit
+ .valueOf(BaseDimensions.MASS, "kilogram", "kg");
+ public static final BaseUnit SECOND = BaseUnit
+ .valueOf(BaseDimensions.TIME, "second", "s");
+ public static final BaseUnit AMPERE = BaseUnit
+ .valueOf(BaseDimensions.ELECTRIC_CURRENT, "ampere", "A");
+ public static final BaseUnit KELVIN = BaseUnit
+ .valueOf(BaseDimensions.TEMPERATURE, "kelvin", "K");
+ public static final BaseUnit MOLE = BaseUnit
+ .valueOf(BaseDimensions.QUANTITY, "mole", "mol");
+ public static final BaseUnit CANDELA = BaseUnit
+ .valueOf(BaseDimensions.LUMINOUS_INTENSITY, "candela", "cd");
+ public static final BaseUnit BIT = BaseUnit
+ .valueOf(BaseDimensions.INFORMATION, "bit", "b");
+ public static final BaseUnit DOLLAR = BaseUnit
+ .valueOf(BaseDimensions.CURRENCY, "dollar", "$");
+
+ public static final Set<BaseUnit> BASE_UNITS = Set.of(METRE, KILOGRAM,
+ SECOND, AMPERE, KELVIN, MOLE, CANDELA, BIT);
+
+ // You may NOT get SI.BaseUnits instances!
+ private BaseUnits() {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Constants that relate to the SI or other systems.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-11-08
+ */
+ public static final class Constants {
+ public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND)
+ .dividedBy(SECOND).times(9.80665);
+ }
+
+ // dimensions used in the SI, as ObjectProducts
+ public static final class Dimensions {
+ public static final ObjectProduct<BaseDimension> EMPTY = ObjectProduct
+ .empty();
+ public static final ObjectProduct<BaseDimension> LENGTH = ObjectProduct
+ .oneOf(BaseDimensions.LENGTH);
+ public static final ObjectProduct<BaseDimension> MASS = ObjectProduct
+ .oneOf(BaseDimensions.MASS);
+ public static final ObjectProduct<BaseDimension> TIME = ObjectProduct
+ .oneOf(BaseDimensions.TIME);
+ public static final ObjectProduct<BaseDimension> ELECTRIC_CURRENT = ObjectProduct
+ .oneOf(BaseDimensions.ELECTRIC_CURRENT);
+ public static final ObjectProduct<BaseDimension> TEMPERATURE = ObjectProduct
+ .oneOf(BaseDimensions.TEMPERATURE);
+ public static final ObjectProduct<BaseDimension> QUANTITY = ObjectProduct
+ .oneOf(BaseDimensions.QUANTITY);
+ public static final ObjectProduct<BaseDimension> LUMINOUS_INTENSITY = ObjectProduct
+ .oneOf(BaseDimensions.LUMINOUS_INTENSITY);
+ public static final ObjectProduct<BaseDimension> INFORMATION = ObjectProduct
+ .oneOf(BaseDimensions.INFORMATION);
+ public static final ObjectProduct<BaseDimension> CURRENCY = ObjectProduct
+ .oneOf(BaseDimensions.CURRENCY);
+
+ // derived dimensions without named SI units
+ public static final ObjectProduct<BaseDimension> AREA = LENGTH
+ .times(LENGTH);
+ public static final ObjectProduct<BaseDimension> VOLUME = AREA
+ .times(LENGTH);
+ public static final ObjectProduct<BaseDimension> VELOCITY = LENGTH
+ .dividedBy(TIME);
+ public static final ObjectProduct<BaseDimension> ACCELERATION = VELOCITY
+ .dividedBy(TIME);
+ public static final ObjectProduct<BaseDimension> WAVENUMBER = EMPTY
+ .dividedBy(LENGTH);
+ public static final ObjectProduct<BaseDimension> MASS_DENSITY = MASS
+ .dividedBy(VOLUME);
+ public static final ObjectProduct<BaseDimension> SURFACE_DENSITY = MASS
+ .dividedBy(AREA);
+ public static final ObjectProduct<BaseDimension> SPECIFIC_VOLUME = VOLUME
+ .dividedBy(MASS);
+ public static final ObjectProduct<BaseDimension> CURRENT_DENSITY = ELECTRIC_CURRENT
+ .dividedBy(AREA);
+ public static final ObjectProduct<BaseDimension> MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT
+ .dividedBy(LENGTH);
+ public static final ObjectProduct<BaseDimension> CONCENTRATION = QUANTITY
+ .dividedBy(VOLUME);
+ public static final ObjectProduct<BaseDimension> MASS_CONCENTRATION = CONCENTRATION
+ .times(MASS);
+ public static final ObjectProduct<BaseDimension> LUMINANCE = LUMINOUS_INTENSITY
+ .dividedBy(AREA);
+ public static final ObjectProduct<BaseDimension> REFRACTIVE_INDEX = VELOCITY
+ .dividedBy(VELOCITY);
+ public static final ObjectProduct<BaseDimension> REFRACTIVE_PERMEABILITY = EMPTY
+ .times(EMPTY);
+ public static final ObjectProduct<BaseDimension> ANGLE = LENGTH
+ .dividedBy(LENGTH);
+ public static final ObjectProduct<BaseDimension> SOLID_ANGLE = AREA
+ .dividedBy(AREA);
+
+ // derived dimensions with named SI units
+ public static final ObjectProduct<BaseDimension> FREQUENCY = EMPTY
+ .dividedBy(TIME);
+ public static final ObjectProduct<BaseDimension> FORCE = MASS
+ .times(ACCELERATION);
+ public static final ObjectProduct<BaseDimension> ENERGY = FORCE
+ .times(LENGTH);
+ public static final ObjectProduct<BaseDimension> POWER = ENERGY
+ .dividedBy(TIME);
+ public static final ObjectProduct<BaseDimension> ELECTRIC_CHARGE = ELECTRIC_CURRENT
+ .times(TIME);
+ public static final ObjectProduct<BaseDimension> VOLTAGE = ENERGY
+ .dividedBy(ELECTRIC_CHARGE);
+ public static final ObjectProduct<BaseDimension> CAPACITANCE = ELECTRIC_CHARGE
+ .dividedBy(VOLTAGE);
+ public static final ObjectProduct<BaseDimension> ELECTRIC_RESISTANCE = VOLTAGE
+ .dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct<BaseDimension> ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT
+ .dividedBy(VOLTAGE);
+ public static final ObjectProduct<BaseDimension> MAGNETIC_FLUX = VOLTAGE
+ .times(TIME);
+ public static final ObjectProduct<BaseDimension> MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX
+ .dividedBy(AREA);
+ public static final ObjectProduct<BaseDimension> INDUCTANCE = MAGNETIC_FLUX
+ .dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct<BaseDimension> LUMINOUS_FLUX = LUMINOUS_INTENSITY
+ .times(SOLID_ANGLE);
+ public static final ObjectProduct<BaseDimension> ILLUMINANCE = LUMINOUS_FLUX
+ .dividedBy(AREA);
+ public static final ObjectProduct<BaseDimension> SPECIFIC_ENERGY = ENERGY
+ .dividedBy(MASS);
+ public static final ObjectProduct<BaseDimension> CATALYTIC_ACTIVITY = QUANTITY
+ .dividedBy(TIME);
+
+ // You may NOT get SI.Dimension instances!
+ private Dimensions() {
+ throw new AssertionError();
+ }
+ }
+
+ /// The units of the SI
+ public static final LinearUnit ONE = LinearUnit
+ .valueOf(ObjectProduct.empty(), 1);
+
+ public static final LinearUnit METRE = BaseUnits.METRE.asLinearUnit()
+ .withName(NameSymbol.of("metre", "m", "meter"));
+ public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit()
+ .withName(NameSymbol.of("kilogram", "kg"));
+ public static final LinearUnit SECOND = BaseUnits.SECOND.asLinearUnit()
+ .withName(NameSymbol.of("second", "s", "sec"));
+ public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit()
+ .withName(NameSymbol.of("ampere", "A"));
+ public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit()
+ .withName(NameSymbol.of("kelvin", "K"));
+ public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit()
+ .withName(NameSymbol.of("mole", "mol"));
+ public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit()
+ .withName(NameSymbol.of("candela", "cd"));
+ public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit()
+ .withName(NameSymbol.of("bit", "b"));
+ public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit()
+ .withName(NameSymbol.of("dollar", "$"));
+ // Non-base units
+ public static final LinearUnit RADIAN = METRE.dividedBy(METRE)
+ .withName(NameSymbol.of("radian", "rad"));
+
+ public static final LinearUnit STERADIAN = RADIAN.times(RADIAN)
+ .withName(NameSymbol.of("steradian", "sr"));
+ public static final LinearUnit HERTZ = ONE.dividedBy(SECOND)
+ .withName(NameSymbol.of("hertz", "Hz"));
+ // for periodic phenomena
+ public static final LinearUnit NEWTON = KILOGRAM.times(METRE)
+ .dividedBy(SECOND.times(SECOND))
+ .withName(NameSymbol.of("newton", "N"));
+ public static final LinearUnit PASCAL = NEWTON.dividedBy(METRE.times(METRE))
+ .withName(NameSymbol.of("pascal", "Pa"));
+ public static final LinearUnit JOULE = NEWTON.times(METRE)
+ .withName(NameSymbol.of("joule", "J"));
+ public static final LinearUnit WATT = JOULE.dividedBy(SECOND)
+ .withName(NameSymbol.of("watt", "W"));
+ public static final LinearUnit COULOMB = AMPERE.times(SECOND)
+ .withName(NameSymbol.of("coulomb", "C"));
+ public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB)
+ .withName(NameSymbol.of("volt", "V"));
+ public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT)
+ .withName(NameSymbol.of("farad", "F"));
+ public static final LinearUnit OHM = VOLT.dividedBy(AMPERE)
+ .withName(NameSymbol.of("ohm", "\u03A9")); // omega
+ public static final LinearUnit SIEMENS = ONE.dividedBy(OHM)
+ .withName(NameSymbol.of("siemens", "S"));
+ public static final LinearUnit WEBER = VOLT.times(SECOND)
+ .withName(NameSymbol.of("weber", "Wb"));
+ public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE))
+ .withName(NameSymbol.of("tesla", "T"));
+ public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE)
+ .withName(NameSymbol.of("henry", "H"));
+ public static final LinearUnit LUMEN = CANDELA.times(STERADIAN)
+ .withName(NameSymbol.of("lumen", "lm"));
+ public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE))
+ .withName(NameSymbol.of("lux", "lx"));
+ public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND)
+ .withName(NameSymbol.of("bequerel", "Bq"));
+ // for activity referred to a nucleotide
+ public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM)
+ .withName(NameSymbol.of("grey", "Gy"));
+ // for absorbed dose
+ public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM)
+ .withName(NameSymbol.of("sievert", "Sv"));
+ // for dose equivalent
+ public static final LinearUnit KATAL = MOLE.dividedBy(SECOND)
+ .withName(NameSymbol.of("katal", "kat"));
+ // common derived units included for convenience
+ public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000)
+ .withName(NameSymbol.of("gram", "g"));
+
+ public static final LinearUnit SQUARE_METRE = METRE.toExponent(2)
+ .withName(NameSymbol.of("square metre", "m^2", "square meter",
+ "metre squared", "meter squared"));
+ public static final LinearUnit CUBIC_METRE = METRE.toExponent(3)
+ .withName(NameSymbol.of("cubic metre", "m^3", "cubic meter",
+ "metre cubed", "meter cubed"));
+ public static final LinearUnit METRE_PER_SECOND = METRE.dividedBy(SECOND)
+ .withName(
+ NameSymbol.of("metre per second", "m/s", "meter per second"));
+ // Non-SI units included for convenience
+ public static final Unit CELSIUS = Unit
+ .fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15,
+ tempC -> tempC + 273.15)
+ .withName(NameSymbol.of("degree Celsius", "\u00B0C"));
+
+ public static final LinearUnit MINUTE = SECOND.times(60)
+ .withName(NameSymbol.of("minute", "min"));
+ public static final LinearUnit HOUR = MINUTE.times(60)
+ .withName(NameSymbol.of("hour", "h", "hr"));
+ public static final LinearUnit DAY = HOUR.times(60)
+ .withName(NameSymbol.of("day", "d"));
+ public static final LinearUnit KILOMETRE_PER_HOUR = METRE.times(1000)
+ .dividedBy(HOUR).withName(NameSymbol.of("kilometre per hour", "km/h",
+ "kilometer per hour"));
+ public static final LinearUnit DEGREE = RADIAN.times(360 / (2 * Math.PI))
+ .withName(NameSymbol.of("degree", "\u00B0", "deg"));
+ public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60)
+ .withName(NameSymbol.of("arcminute", "arcmin"));
+ public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60)
+ .withName(NameSymbol.of("arcsecond", "arcsec"));
+ public static final LinearUnit ASTRONOMICAL_UNIT = METRE
+ .times(149597870700.0)
+ .withName(NameSymbol.of("astronomical unit", "au"));
+ public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT
+ .dividedBy(ARCSECOND).withName(NameSymbol.of("parsec", "pc"));
+ public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0)
+ .withName(NameSymbol.of("hectare", "ha"));
+ public static final LinearUnit LITRE = METRE.times(METRE).times(METRE)
+ .dividedBy(1000.0).withName(NameSymbol.of("litre", "L", "l", "liter"));
+ public static final LinearUnit TONNE = KILOGRAM.times(1000.0)
+ .withName(NameSymbol.of("tonne", "t", "metric ton"));
+ public static final LinearUnit DALTON = KILOGRAM.times(1.660539040e-27)
+ .withName(NameSymbol.of("dalton", "Da", "atomic unit", "u")); // approximate
+ // value
+ public static final LinearUnit ELECTRONVOLT = JOULE.times(1.602176634e-19)
+ .withName(NameSymbol.of("electron volt", "eV"));
+ public static final LinearUnit BYTE = BIT.times(8)
+ .withName(NameSymbol.of("byte", "B"));
+ public static final Unit NEPER = Unit.fromConversionFunctions(ONE.getBase(),
+ pr -> 0.5 * Math.log(pr), Np -> Math.exp(2 * Np))
+ .withName(NameSymbol.of("neper", "Np"));
+ public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(),
+ pr -> Math.log10(pr), dB -> Math.pow(10, dB))
+ .withName(NameSymbol.of("bel", "B"));
+ public static final Unit DECIBEL = Unit
+ .fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr),
+ dB -> Math.pow(10, dB / 10))
+ .withName(NameSymbol.of("decibel", "dB"));
+
+ /// The prefixes of the SI
+ // expanding decimal prefixes
+ public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3)
+ .withName(NameSymbol.of("kilo", "k", "K"));
+ public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6)
+ .withName(NameSymbol.of("mega", "M"));
+ public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9)
+ .withName(NameSymbol.of("giga", "G"));
+ public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12)
+ .withName(NameSymbol.of("tera", "T"));
+ public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15)
+ .withName(NameSymbol.of("peta", "P"));
+ public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18)
+ .withName(NameSymbol.of("exa", "E"));
+ public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21)
+ .withName(NameSymbol.of("zetta", "Z"));
+ public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24)
+ .withName(NameSymbol.of("yotta", "Y"));
+
+ // contracting decimal prefixes
+ public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3)
+ .withName(NameSymbol.of("milli", "m"));
+ public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6)
+ .withName(NameSymbol.of("micro", "\u03BC", "u")); // mu
+ public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9)
+ .withName(NameSymbol.of("nano", "n"));
+ public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12)
+ .withName(NameSymbol.of("pico", "p"));
+ public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15)
+ .withName(NameSymbol.of("femto", "f"));
+ public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18)
+ .withName(NameSymbol.of("atto", "a"));
+ public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21)
+ .withName(NameSymbol.of("zepto", "z"));
+ public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24)
+ .withName(NameSymbol.of("yocto", "y"));
+
+ // prefixes that don't match the pattern of thousands
+ public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1)
+ .withName(NameSymbol.of("deka", "da", "deca", "D"));
+ public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2)
+ .withName(NameSymbol.of("hecto", "h", "H", "hekto"));
+ public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1)
+ .withName(NameSymbol.of("deci", "d"));
+ public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2)
+ .withName(NameSymbol.of("centi", "c"));
+ public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024)
+ .withName(NameSymbol.of("kibi", "Ki"));
+ public static final UnitPrefix MEBI = KIBI.times(1024)
+ .withName(NameSymbol.of("mebi", "Mi"));
+ public static final UnitPrefix GIBI = MEBI.times(1024)
+ .withName(NameSymbol.of("gibi", "Gi"));
+ public static final UnitPrefix TEBI = GIBI.times(1024)
+ .withName(NameSymbol.of("tebi", "Ti"));
+ public static final UnitPrefix PEBI = TEBI.times(1024)
+ .withName(NameSymbol.of("pebi", "Pi"));
+ public static final UnitPrefix EXBI = PEBI.times(1024)
+ .withName(NameSymbol.of("exbi", "Ei"));
+
+ // a few prefixed units
+ public static final LinearUnit MICROMETRE = SI.METRE.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLIMETRE = SI.METRE.withPrefix(SI.MILLI);
+ public static final LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
+ public static final LinearUnit MEGAMETRE = SI.METRE.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICROLITRE = SI.LITRE.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLILITRE = SI.LITRE.withPrefix(SI.MILLI);
+ public static final LinearUnit KILOLITRE = SI.LITRE.withPrefix(SI.KILO);
+ public static final LinearUnit MEGALITRE = SI.LITRE.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICROSECOND = SI.SECOND.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLISECOND = SI.SECOND.withPrefix(SI.MILLI);
+ public static final LinearUnit KILOSECOND = SI.SECOND.withPrefix(SI.KILO);
+ public static final LinearUnit MEGASECOND = SI.SECOND.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICROGRAM = SI.GRAM.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLIGRAM = SI.GRAM.withPrefix(SI.MILLI);
+ public static final LinearUnit MEGAGRAM = SI.GRAM.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICRONEWTON = SI.NEWTON.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLINEWTON = SI.NEWTON.withPrefix(SI.MILLI);
+ public static final LinearUnit KILONEWTON = SI.NEWTON.withPrefix(SI.KILO);
+ public static final LinearUnit MEGANEWTON = SI.NEWTON.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICROJOULE = SI.JOULE.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLIJOULE = SI.JOULE.withPrefix(SI.MILLI);
+ public static final LinearUnit KILOJOULE = SI.JOULE.withPrefix(SI.KILO);
+ public static final LinearUnit MEGAJOULE = SI.JOULE.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICROWATT = SI.WATT.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLIWATT = SI.WATT.withPrefix(SI.MILLI);
+ public static final LinearUnit KILOWATT = SI.WATT.withPrefix(SI.KILO);
+ public static final LinearUnit MEGAWATT = SI.WATT.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICROCOULOMB = SI.COULOMB
+ .withPrefix(SI.MICRO);
+ public static final LinearUnit MILLICOULOMB = SI.COULOMB
+ .withPrefix(SI.MILLI);
+ public static final LinearUnit KILOCOULOMB = SI.COULOMB.withPrefix(SI.KILO);
+ public static final LinearUnit MEGACOULOMB = SI.COULOMB.withPrefix(SI.MEGA);
+
+ public static final LinearUnit MICROAMPERE = SI.AMPERE.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLIAMPERE = SI.AMPERE.withPrefix(SI.MILLI);
+
+ public static final LinearUnit MICROVOLT = SI.VOLT.withPrefix(SI.MICRO);
+ public static final LinearUnit MILLIVOLT = SI.VOLT.withPrefix(SI.MILLI);
+ public static final LinearUnit KILOVOLT = SI.VOLT.withPrefix(SI.KILO);
+ public static final LinearUnit MEGAVOLT = SI.VOLT.withPrefix(SI.MEGA);
+
+ public static final LinearUnit KILOOHM = SI.OHM.withPrefix(SI.KILO);
+ public static final LinearUnit MEGAOHM = SI.OHM.withPrefix(SI.MEGA);
+
+ // sets of prefixes
+ public static final Set<UnitPrefix> ALL_PREFIXES = Set.of(DEKA, HECTO, KILO,
+ MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO,
+ NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO, KIBI, MEBI, GIBI, TEBI, PEBI,
+ EXBI);
+
+ public static final Set<UnitPrefix> DECIMAL_PREFIXES = Set.of(DEKA, HECTO,
+ KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI,
+ MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO);
+ public static final Set<UnitPrefix> THOUSAND_PREFIXES = Set.of(KILO, MEGA,
+ GIGA, TERA, PETA, EXA, ZETTA, YOTTA, MILLI, MICRO, NANO, PICO, FEMTO,
+ ATTO, ZEPTO, YOCTO);
+ public static final Set<UnitPrefix> MAGNIFYING_PREFIXES = Set.of(DEKA, HECTO,
+ KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, KIBI, MEBI, GIBI,
+ TEBI, PEBI, EXBI);
+ public static final Set<UnitPrefix> REDUCING_PREFIXES = Set.of(DECI, CENTI,
+ MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO);
+
+ // You may NOT get SI instances!
+ private SI() {
+ throw new AssertionError();
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/USCustomary.java b/src/main/java/org/unitConverter/unit/USCustomary.java
new file mode 100644
index 0000000..1c4bcfe
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/USCustomary.java
@@ -0,0 +1,135 @@
+/**
+ * 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;
+
+/**
+ * A static utility class that contains units in the US Customary system.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-21
+ */
+public final class USCustomary {
+ /**
+ * US Customary units that measure area
+ *
+ * @author Adrien Hopkins
+ * @since 2019-11-08
+ */
+ public static final class Area {
+ public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT.times(Length.SURVEY_FOOT);
+ public static final LinearUnit SQUARE_CHAIN = Length.SURVEY_CHAIN.times(Length.SURVEY_CHAIN);
+ public static final LinearUnit ACRE = Length.SURVEY_CHAIN.times(Length.SURVEY_FURLONG);
+ public static final LinearUnit SECTION = Length.SURVEY_MILE.times(Length.SURVEY_MILE);
+ public static final LinearUnit SURVEY_TOWNSHIP = SECTION.times(36);
+ }
+
+ /**
+ * US Customary units that measure length
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-28
+ */
+ public static final class Length {
+ public static final LinearUnit FOOT = BritishImperial.Length.FOOT;
+ public static final LinearUnit INCH = BritishImperial.Length.INCH;
+ public static final LinearUnit HAND = INCH.times(4);
+ public static final LinearUnit PICA = INCH.dividedBy(6);
+ public static final LinearUnit POINT = PICA.dividedBy(12);
+ public static final LinearUnit YARD = BritishImperial.Length.YARD;
+ public static final LinearUnit MILE = BritishImperial.Length.MILE;
+
+ public static final LinearUnit SURVEY_FOOT = SI.METRE.times(1200.0 / 3937.0);
+ public static final LinearUnit SURVEY_LINK = SURVEY_FOOT.times(33.0 / 50.0);
+ public static final LinearUnit SURVEY_ROD = SURVEY_FOOT.times(16.5);
+ public static final LinearUnit SURVEY_CHAIN = SURVEY_ROD.times(4);
+ public static final LinearUnit SURVEY_FURLONG = SURVEY_CHAIN.times(10);
+ public static final LinearUnit SURVEY_MILE = SURVEY_FURLONG.times(8);
+ public static final LinearUnit SURVEY_LEAGUE = SURVEY_MILE.times(3);
+
+ public static final LinearUnit NAUTICAL_MILE = BritishImperial.Length.NAUTICAL_MILE;
+ public static final LinearUnit FATHOM = YARD.times(2);
+ public static final LinearUnit CABLE = FATHOM.times(120);
+ }
+
+ /**
+ * mass units
+ *
+ * @author Adrien Hopkins
+ * @since 2019-11-08
+ */
+ public static final class Mass {
+ public static final LinearUnit GRAIN = BritishImperial.Mass.GRAIN;
+ public static final LinearUnit DRAM = BritishImperial.Mass.DRACHM;
+ public static final LinearUnit OUNCE = BritishImperial.Mass.OUNCE;
+ public static final LinearUnit POUND = BritishImperial.Mass.POUND;
+ public static final LinearUnit HUNDREDWEIGHT = POUND.times(100);
+ public static final LinearUnit SHORT_TON = HUNDREDWEIGHT.times(20);
+
+ // troy system for precious metals
+ public static final LinearUnit PENNYWEIGHT = GRAIN.times(24);
+ public static final LinearUnit TROY_OUNCE = PENNYWEIGHT.times(20);
+ public static final LinearUnit TROY_POUND = TROY_OUNCE.times(12);
+ }
+
+ /**
+ * Volume units
+ *
+ * @author Adrien Hopkins
+ * @since 2019-11-08
+ */
+ public static final class Volume {
+ public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3);
+ public static final LinearUnit CUBIC_FOOT = Length.FOOT.toExponent(3);
+ public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3);
+ public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT);
+
+ public static final LinearUnit MINIM = SI.LITRE.withPrefix(SI.MICRO).times(61.611519921875);
+ public static final LinearUnit FLUID_DRAM = MINIM.times(60);
+ public static final LinearUnit TEASPOON = MINIM.times(80);
+ public static final LinearUnit TABLESPOON = TEASPOON.times(3);
+ public static final LinearUnit FLUID_OUNCE = TABLESPOON.times(2);
+ public static final LinearUnit SHOT = TABLESPOON.times(3);
+ public static final LinearUnit GILL = FLUID_OUNCE.times(4);
+ public static final LinearUnit CUP = GILL.times(2);
+ public static final LinearUnit PINT = CUP.times(2);
+ public static final LinearUnit QUART = PINT.times(2);
+ public static final LinearUnit GALLON = QUART.times(4);
+ public static final LinearUnit BARREL = GALLON.times(31.5);
+ public static final LinearUnit OIL_BARREL = GALLON.times(42);
+ public static final LinearUnit HOGSHEAD = GALLON.times(63);
+
+ public static final LinearUnit DRY_PINT = SI.LITRE.times(0.5506104713575);
+ public static final LinearUnit DRY_QUART = DRY_PINT.times(2);
+ public static final LinearUnit DRY_GALLON = DRY_QUART.times(4);
+ public static final LinearUnit PECK = DRY_GALLON.times(2);
+ public static final LinearUnit BUSHEL = PECK.times(4);
+ public static final LinearUnit DRY_BARREL = CUBIC_INCH.times(7056);
+ }
+
+ public static final LinearUnit OUNCE_FORCE = BritishImperial.OUNCE_FORCE;
+ public static final LinearUnit POUND_FORCE = BritishImperial.POUND_FORCE;
+
+ public static final LinearUnit BRITISH_THERMAL_UNIT = BritishImperial.BRITISH_THERMAL_UNIT;
+ public static final LinearUnit CALORIE = BritishImperial.CALORIE;
+ public static final LinearUnit KILOCALORIE = BritishImperial.KILOCALORIE;
+ public static final LinearUnit FOOT_POUND = POUND_FORCE.times(Length.FOOT);
+
+ public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE).dividedBy(SI.MINUTE).times(33000);
+ public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE.dividedBy(Length.INCH.toExponent(2));
+
+ public static final Unit FAHRENHEIT = BritishImperial.FAHRENHEIT;
+}
diff --git a/src/main/java/org/unitConverter/unit/Unit.java b/src/main/java/org/unitConverter/unit/Unit.java
new file mode 100644
index 0000000..0a3298f
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/Unit.java
@@ -0,0 +1,377 @@
+/**
+ * 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.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.DoubleUnaryOperator;
+
+import org.unitConverter.math.DecimalComparison;
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * A unit that is composed of base units.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public abstract class Unit implements Nameable {
+ /**
+ * 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:
+ *
+ * {@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.
+ * @return a unit that uses the provided functions to convert.
+ * @since 2019-05-22
+ * @throws NullPointerException if any argument is null
+ */
+ 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.
+ *
+ * <p>
+ * 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
+ * @return a unit that uses the provided functions to convert.
+ * @since 2019-05-22
+ * @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) {
+ 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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
+ */
+ 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) {
+ if (this instanceof BaseUnit) {
+ this.unitBase = ObjectProduct.oneOf((BaseUnit) this);
+ } else
+ throw new AssertionError();
+ 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 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 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>
+ * 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}.
+ * </p>
+ *
+ * @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
+ * @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}.
+ *
+ * @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.
+ *
+ * @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
+ */
+ 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));
+ }
+
+ /**
+ * 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>
+ * 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}.
+ * </p>
+ *
+ * @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
+ * @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
+ * @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;
+ }
+
+ /**
+ * 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 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()));
+ }
+
+ /**
+ * @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
+ */
+ public Unit withName(final NameSymbol ns) {
+ return fromConversionFunctions(this.getBase(), this::convertFromBase,
+ this::convertToBase,
+ Objects.requireNonNull(ns, "ns must not be null."));
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/UnitDatabase.java b/src/main/java/org/unitConverter/unit/UnitDatabase.java
new file mode 100644
index 0000000..000acf5
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/UnitDatabase.java
@@ -0,0 +1,1991 @@
+/**
+ * Copyright (C) 2018 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.io.FileNotFoundException;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.unitConverter.math.ConditionalExistenceCollections;
+import org.unitConverter.math.DecimalComparison;
+import org.unitConverter.math.ExpressionParser;
+import org.unitConverter.math.ObjectProduct;
+import org.unitConverter.math.UncertainDouble;
+
+/**
+ * A database of units, prefixes and dimensions, and their names.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+public final class UnitDatabase {
+ /**
+ * A map for units that allows the use of prefixes.
+ * <p>
+ * As this map implementation is intended to be used as a sort of "augmented
+ * view" of a unit and prefix map, it is unmodifiable but instead reflects
+ * the changes to the maps passed into it. Do not edit this map, instead edit
+ * the maps that were passed in during construction.
+ * </p>
+ * <p>
+ * The rules for applying prefixes onto units are the following:
+ * <ul>
+ * <li>Prefixes can only be applied to linear units.</li>
+ * <li>Before attempting to search for prefixes in a unit name, this map will
+ * first search for a unit name. So, if there are two units, "B" and "AB",
+ * and a prefix "A", this map will favour the unit "AB" over the unit "B"
+ * with the prefix "A", even though they have the same string.</li>
+ * <li>Longer prefixes are preferred to shorter prefixes. So, if you have
+ * units "BC" and "C", and prefixes "AB" and "A", inputting "ABC" will return
+ * the unit "C" with the prefix "AB", not "BC" with the prefix "A".</li>
+ * </ul>
+ * </p>
+ * <p>
+ * This map is infinite in size if there is at least one unit and at least
+ * one prefix. If it is infinite, some operations that only work with finite
+ * collections, like converting name/entry sets to arrays, will throw an
+ * {@code IllegalStateException}.
+ * </p>
+ * <p>
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega),
+ * {@link #containsValue} and {@link #values()} currently ignore prefixes.
+ * </p>
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitMap implements Map<String, Unit> {
+ /**
+ * The class used for entry sets.
+ *
+ * <p>
+ * If the map that created this set is infinite in size (has at least one
+ * unit and at least one prefix), this set is infinite as well. If this
+ * set is infinite in size, {@link #toArray} will fail with a
+ * {@code IllegalStateException} instead of creating an infinite-sized
+ * array.
+ * </p>
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitEntrySet
+ extends AbstractSet<Map.Entry<String, Unit>> {
+ /**
+ * The entry for this set.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitEntry
+ implements Entry<String, Unit> {
+ private final String key;
+ private final Unit value;
+
+ /**
+ * Creates the {@code PrefixedUnitEntry}.
+ *
+ * @param key key
+ * @param value value
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ public PrefixedUnitEntry(final String key, final Unit value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * @since 2019-05-03
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (!(o instanceof Map.Entry))
+ return false;
+ final Map.Entry<?, ?> other = (Map.Entry<?, ?>) o;
+ return Objects.equals(this.getKey(), other.getKey())
+ && Objects.equals(this.getValue(), other.getValue());
+ }
+
+ @Override
+ public String getKey() {
+ return this.key;
+ }
+
+ @Override
+ public Unit getValue() {
+ return this.value;
+ }
+
+ /**
+ * @since 2019-05-03
+ */
+ @Override
+ public int hashCode() {
+ return (this.getKey() == null ? 0 : this.getKey().hashCode())
+ ^ (this.getValue() == null ? 0
+ : this.getValue().hashCode());
+ }
+
+ @Override
+ public Unit setValue(final Unit value) {
+ throw new UnsupportedOperationException(
+ "Cannot set value in an immutable entry");
+ }
+
+ /**
+ * Returns a string representation of the entry. The format of the
+ * string is the string representation of the key, then the equals
+ * ({@code =}) character, then the string representation of the
+ * value.
+ *
+ * @since 2019-05-03
+ */
+ @Override
+ public String toString() {
+ return this.getKey() + "=" + this.getValue();
+ }
+ }
+
+ /**
+ * An iterator that iterates over the units of a
+ * {@code PrefixedUnitNameSet}.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitEntryIterator
+ implements Iterator<Entry<String, Unit>> {
+ // position in the unit list
+ private int unitNamePosition = 0;
+ // the indices of the prefixes attached to the current unit
+ private final List<Integer> prefixCoordinates = new ArrayList<>();
+
+ // values from the unit entry set
+ private final Map<String, Unit> map;
+ private transient final List<String> unitNames;
+ private transient final List<String> prefixNames;
+
+ /**
+ * Creates the
+ * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ public PrefixedUnitEntryIterator(final PrefixedUnitMap map) {
+ this.map = map;
+ this.unitNames = new ArrayList<>(map.units.keySet());
+ this.prefixNames = new ArrayList<>(map.prefixes.keySet());
+ }
+
+ /**
+ * @return current unit name
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private String getCurrentUnitName() {
+ final StringBuilder unitName = new StringBuilder();
+ for (final int i : this.prefixCoordinates) {
+ unitName.append(this.prefixNames.get(i));
+ }
+ unitName.append(this.unitNames.get(this.unitNamePosition));
+
+ return unitName.toString();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (this.unitNames.isEmpty())
+ return false;
+ else {
+ if (this.prefixNames.isEmpty())
+ return this.unitNamePosition >= this.unitNames.size() - 1;
+ else
+ return true;
+ }
+ }
+
+ /**
+ * Changes this iterator's position to the next available one.
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private void incrementPosition() {
+ this.unitNamePosition++;
+
+ if (this.unitNamePosition >= this.unitNames.size()) {
+ // we have used all of our units, go to a different prefix
+ this.unitNamePosition = 0;
+
+ // if the prefix coordinates are empty, then set it to [0]
+ if (this.prefixCoordinates.isEmpty()) {
+ this.prefixCoordinates.add(0, 0);
+ } else {
+ // get the prefix coordinate to increment, then increment
+ int i = this.prefixCoordinates.size() - 1;
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
+
+ // fix any carrying errors
+ while (i >= 0 && this.prefixCoordinates
+ .get(i) >= this.prefixNames.size()) {
+ // carry over
+ this.prefixCoordinates.set(i--, 0); // null and
+ // decrement at the
+ // same time
+
+ if (i < 0) { // we need to add a new coordinate
+ this.prefixCoordinates.add(0, 0);
+ } else { // increment an existing one
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public Entry<String, Unit> next() {
+ // get next element
+ final Entry<String, Unit> nextEntry = this.peek();
+
+ // iterate to next position
+ this.incrementPosition();
+
+ return nextEntry;
+ }
+
+ /**
+ * @return the next element in the iterator, without iterating over
+ * it
+ * @since 2019-05-03
+ */
+ private Entry<String, Unit> peek() {
+ if (!this.hasNext())
+ throw new NoSuchElementException("No units left!");
+
+ // if I have prefixes, ensure I'm not using a nonlinear unit
+ // since all of the unprefixed stuff is done, just remove
+ // nonlinear units
+ if (!this.prefixCoordinates.isEmpty()) {
+ while (this.unitNamePosition < this.unitNames.size()
+ && !(this.map.get(this.unitNames.get(
+ this.unitNamePosition)) instanceof LinearUnit)) {
+ this.unitNames.remove(this.unitNamePosition);
+ }
+ }
+
+ final String nextName = this.getCurrentUnitName();
+
+ return new PrefixedUnitEntry(nextName, this.map.get(nextName));
+ }
+
+ /**
+ * Returns a string representation of the object. The exact details
+ * of the representation are unspecified and subject to change.
+ *
+ * @since 2019-05-03
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ "Iterator iterating over name-unit entries; next value is \"%s\"",
+ this.peek());
+ }
+ }
+
+ // the map that created this set
+ private final PrefixedUnitMap map;
+
+ /**
+ * Creates the {@code PrefixedUnitNameSet}.
+ *
+ * @param map map that created this set
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public PrefixedUnitEntrySet(final PrefixedUnitMap map) {
+ this.map = map;
+ }
+
+ @Override
+ public boolean add(final Map.Entry<String, Unit> e) {
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
+ }
+
+ @Override
+ public boolean addAll(
+ final Collection<? extends Map.Entry<String, Unit>> c) {
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException(
+ "Cannot clear an immutable set");
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ // get the entry
+ final Entry<String, Unit> entry;
+
+ try {
+ // This is OK because I'm in a try-catch block, catching the
+ // exact exception that would be thrown.
+ @SuppressWarnings("unchecked")
+ final Entry<String, Unit> tempEntry = (Entry<String, Unit>) o;
+ entry = tempEntry;
+ } catch (final ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Attempted to test for an entry using a non-entry.");
+ }
+
+ return this.map.containsKey(entry.getKey())
+ && this.map.get(entry.getKey()).equals(entry.getValue());
+ }
+
+ @Override
+ public boolean containsAll(final Collection<?> c) {
+ for (final Object o : c)
+ if (!this.contains(o))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.map.isEmpty();
+ }
+
+ @Override
+ public Iterator<Entry<String, Unit>> iterator() {
+ return new PrefixedUnitEntryIterator(this.map);
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public boolean removeAll(final Collection<?> c) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public boolean removeIf(
+ final Predicate<? super Entry<String, Unit>> filter) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public boolean retainAll(final Collection<?> c) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public int size() {
+ if (this.map.units.isEmpty())
+ return 0;
+ else {
+ if (this.map.prefixes.isEmpty())
+ return this.map.units.size();
+ else
+ // infinite set
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ /**
+ * @throws IllegalStateException if the set is infinite in size
+ */
+ @Override
+ public Object[] toArray() {
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
+ return super.toArray();
+ else
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
+ }
+
+ /**
+ * @throws IllegalStateException if the set is infinite in size
+ */
+ @Override
+ public <T> T[] toArray(final T[] a) {
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
+ return super.toArray(a);
+ else
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
+ }
+
+ @Override
+ public String toString() {
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
+ return super.toString();
+ else
+ return String.format(
+ "Infinite set of name-unit entries created from units %s and prefixes %s",
+ this.map.units, this.map.prefixes);
+ }
+ }
+
+ /**
+ * The class used for unit name sets.
+ *
+ * <p>
+ * If the map that created this set is infinite in size (has at least one
+ * unit and at least one prefix), this set is infinite as well. If this
+ * set is infinite in size, {@link #toArray} will fail with a
+ * {@code IllegalStateException} instead of creating an infinite-sized
+ * array.
+ * </p>
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitNameSet
+ extends AbstractSet<String> {
+ /**
+ * An iterator that iterates over the units of a
+ * {@code PrefixedUnitNameSet}.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitNameIterator
+ implements Iterator<String> {
+ // position in the unit list
+ private int unitNamePosition = 0;
+ // the indices of the prefixes attached to the current unit
+ private final List<Integer> prefixCoordinates = new ArrayList<>();
+
+ // values from the unit name set
+ private final Map<String, Unit> map;
+ private transient final List<String> unitNames;
+ private transient final List<String> prefixNames;
+
+ /**
+ * Creates the
+ * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ public PrefixedUnitNameIterator(final PrefixedUnitMap map) {
+ this.map = map;
+ this.unitNames = new ArrayList<>(map.units.keySet());
+ this.prefixNames = new ArrayList<>(map.prefixes.keySet());
+ }
+
+ /**
+ * @return current unit name
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private String getCurrentUnitName() {
+ final StringBuilder unitName = new StringBuilder();
+ for (final int i : this.prefixCoordinates) {
+ unitName.append(this.prefixNames.get(i));
+ }
+ unitName.append(this.unitNames.get(this.unitNamePosition));
+
+ return unitName.toString();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (this.unitNames.isEmpty())
+ return false;
+ else {
+ if (this.prefixNames.isEmpty())
+ return this.unitNamePosition >= this.unitNames.size() - 1;
+ else
+ return true;
+ }
+ }
+
+ /**
+ * Changes this iterator's position to the next available one.
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private void incrementPosition() {
+ this.unitNamePosition++;
+
+ if (this.unitNamePosition >= this.unitNames.size()) {
+ // we have used all of our units, go to a different prefix
+ this.unitNamePosition = 0;
+
+ // if the prefix coordinates are empty, then set it to [0]
+ if (this.prefixCoordinates.isEmpty()) {
+ this.prefixCoordinates.add(0, 0);
+ } else {
+ // get the prefix coordinate to increment, then increment
+ int i = this.prefixCoordinates.size() - 1;
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
+
+ // fix any carrying errors
+ while (i >= 0 && this.prefixCoordinates
+ .get(i) >= this.prefixNames.size()) {
+ // carry over
+ this.prefixCoordinates.set(i--, 0); // null and
+ // decrement at the
+ // same time
+
+ if (i < 0) { // we need to add a new coordinate
+ this.prefixCoordinates.add(0, 0);
+ } else { // increment an existing one
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public String next() {
+ final String nextName = this.peek();
+
+ this.incrementPosition();
+
+ return nextName;
+ }
+
+ /**
+ * @return the next element in the iterator, without iterating over
+ * it
+ * @since 2019-05-03
+ */
+ private String peek() {
+ if (!this.hasNext())
+ throw new NoSuchElementException("No units left!");
+ // if I have prefixes, ensure I'm not using a nonlinear unit
+ // since all of the unprefixed stuff is done, just remove
+ // nonlinear units
+ if (!this.prefixCoordinates.isEmpty()) {
+ while (this.unitNamePosition < this.unitNames.size()
+ && !(this.map.get(this.unitNames.get(
+ this.unitNamePosition)) instanceof LinearUnit)) {
+ this.unitNames.remove(this.unitNamePosition);
+ }
+ }
+
+ return this.getCurrentUnitName();
+ }
+
+ /**
+ * Returns a string representation of the object. The exact details
+ * of the representation are unspecified and subject to change.
+ *
+ * @since 2019-05-03
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ "Iterator iterating over unit names; next value is \"%s\"",
+ this.peek());
+ }
+ }
+
+ // the map that created this set
+ private final PrefixedUnitMap map;
+
+ /**
+ * Creates the {@code PrefixedUnitNameSet}.
+ *
+ * @param map map that created this set
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public PrefixedUnitNameSet(final PrefixedUnitMap map) {
+ this.map = map;
+ }
+
+ @Override
+ public boolean add(final String e) {
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
+ }
+
+ @Override
+ public boolean addAll(final Collection<? extends String> c) {
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException(
+ "Cannot clear an immutable set");
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ return this.map.containsKey(o);
+ }
+
+ @Override
+ public boolean containsAll(final Collection<?> c) {
+ for (final Object o : c)
+ if (!this.contains(o))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.map.isEmpty();
+ }
+
+ @Override
+ public Iterator<String> iterator() {
+ return new PrefixedUnitNameIterator(this.map);
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public boolean removeAll(final Collection<?> c) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public boolean removeIf(final Predicate<? super String> filter) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public boolean retainAll(final Collection<?> c) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
+ }
+
+ @Override
+ public int size() {
+ if (this.map.units.isEmpty())
+ return 0;
+ else {
+ if (this.map.prefixes.isEmpty())
+ return this.map.units.size();
+ else
+ // infinite set
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ /**
+ * @throws IllegalStateException if the set is infinite in size
+ */
+ @Override
+ public Object[] toArray() {
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
+ return super.toArray();
+ else
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
+
+ }
+
+ /**
+ * @throws IllegalStateException if the set is infinite in size
+ */
+ @Override
+ public <T> T[] toArray(final T[] a) {
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
+ return super.toArray(a);
+ else
+ // infinite set
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
+ }
+
+ @Override
+ public String toString() {
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
+ return super.toString();
+ else
+ return String.format(
+ "Infinite set of name-unit entries created from units %s and prefixes %s",
+ this.map.units, this.map.prefixes);
+ }
+ }
+
+ /**
+ * The units stored in this collection, without prefixes.
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final Map<String, Unit> units;
+
+ /**
+ * The available prefixes for use.
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final Map<String, UnitPrefix> prefixes;
+
+ // caches
+ private transient Collection<Unit> values = null;
+ private transient Set<String> keySet = null;
+ private transient Set<Entry<String, Unit>> entrySet = null;
+
+ /**
+ * Creates the {@code PrefixedUnitMap}.
+ *
+ * @param units map mapping unit names to units
+ * @param prefixes map mapping prefix names to prefixes
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public PrefixedUnitMap(final Map<String, Unit> units,
+ final Map<String, UnitPrefix> prefixes) {
+ // I am making unmodifiable maps to ensure I don't accidentally make
+ // changes.
+ this.units = Collections.unmodifiableMap(units);
+ this.prefixes = Collections.unmodifiableMap(prefixes);
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException(
+ "Cannot clear an immutable map");
+ }
+
+ @Override
+ public Unit compute(final String key,
+ final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
+ throw new UnsupportedOperationException(
+ "Cannot edit an immutable map");
+ }
+
+ @Override
+ public Unit computeIfAbsent(final String key,
+ final Function<? super String, ? extends Unit> mappingFunction) {
+ throw new UnsupportedOperationException(
+ "Cannot edit an immutable map");
+ }
+
+ @Override
+ public Unit computeIfPresent(final String key,
+ final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
+ throw new UnsupportedOperationException(
+ "Cannot edit an immutable map");
+ }
+
+ @Override
+ public boolean containsKey(final Object key) {
+ // First, test if there is a unit with the key
+ if (this.units.containsKey(key))
+ return true;
+
+ // Next, try to cast it to String
+ if (!(key instanceof String))
+ throw new IllegalArgumentException(
+ "Attempted to test for a unit using a non-string name.");
+ final String unitName = (String) key;
+
+ // Then, look for the longest prefix that is attached to a valid unit
+ String longestPrefix = null;
+ int longestLength = 0;
+
+ for (final String prefixName : this.prefixes.keySet()) {
+ // a prefix name is valid if:
+ // - it is prefixed (i.e. the unit name starts with it)
+ // - it is longer than the existing largest prefix (since I am
+ // looking for the longest valid prefix)
+ // - the part after the prefix is a valid unit name
+ // - the unit described that name is a linear unit (since only
+ // linear units can have prefixes)
+ if (unitName.startsWith(prefixName)
+ && prefixName.length() > longestLength) {
+ final String rest = unitName.substring(prefixName.length());
+ if (this.containsKey(rest)
+ && this.get(rest) instanceof LinearUnit) {
+ longestPrefix = prefixName;
+ longestLength = prefixName.length();
+ }
+ }
+ }
+
+ return longestPrefix != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), this
+ * method only tests for prefixless units.
+ * </p>
+ */
+ @Override
+ public boolean containsValue(final Object value) {
+ return this.units.containsValue(value);
+ }
+
+ @Override
+ public Set<Entry<String, Unit>> entrySet() {
+ if (this.entrySet == null) {
+ this.entrySet = new PrefixedUnitEntrySet(this);
+ }
+ return this.entrySet;
+ }
+
+ @Override
+ public Unit get(final Object key) {
+ // First, test if there is a unit with the key
+ if (this.units.containsKey(key))
+ return this.units.get(key);
+
+ // Next, try to cast it to String
+ if (!(key instanceof String))
+ throw new IllegalArgumentException(
+ "Attempted to obtain a unit using a non-string name.");
+ final String unitName = (String) key;
+
+ // Then, look for the longest prefix that is attached to a valid unit
+ String longestPrefix = null;
+ int longestLength = 0;
+
+ for (final String prefixName : this.prefixes.keySet()) {
+ // a prefix name is valid if:
+ // - it is prefixed (i.e. the unit name starts with it)
+ // - it is longer than the existing largest prefix (since I am
+ // looking for the longest valid prefix)
+ // - the part after the prefix is a valid unit name
+ // - the unit described that name is a linear unit (since only
+ // linear units can have prefixes)
+ if (unitName.startsWith(prefixName)
+ && prefixName.length() > longestLength) {
+ final String rest = unitName.substring(prefixName.length());
+ if (this.containsKey(rest)
+ && this.get(rest) instanceof LinearUnit) {
+ longestPrefix = prefixName;
+ longestLength = prefixName.length();
+ }
+ }
+ }
+
+ // if none found, returns null
+ if (longestPrefix == null)
+ return null;
+ else {
+ // get necessary data
+ final String rest = unitName.substring(longestLength);
+ // this cast will not fail because I verified that it would work
+ // before selecting this prefix
+ final LinearUnit unit = (LinearUnit) this.get(rest);
+ final UnitPrefix prefix = this.prefixes.get(longestPrefix);
+
+ return unit.withPrefix(prefix);
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.units.isEmpty();
+ }
+
+ @Override
+ public Set<String> keySet() {
+ if (this.keySet == null) {
+ this.keySet = new PrefixedUnitNameSet(this);
+ }
+ return this.keySet;
+ }
+
+ @Override
+ public Unit merge(final String key, final Unit value,
+ final BiFunction<? super Unit, ? super Unit, ? extends Unit> remappingFunction) {
+ throw new UnsupportedOperationException(
+ "Cannot merge into an immutable map");
+ }
+
+ @Override
+ public Unit put(final String key, final Unit value) {
+ throw new UnsupportedOperationException(
+ "Cannot add entries to an immutable map");
+ }
+
+ @Override
+ public void putAll(final Map<? extends String, ? extends Unit> m) {
+ throw new UnsupportedOperationException(
+ "Cannot add entries to an immutable map");
+ }
+
+ @Override
+ public Unit putIfAbsent(final String key, final Unit value) {
+ throw new UnsupportedOperationException(
+ "Cannot add entries to an immutable map");
+ }
+
+ @Override
+ public Unit remove(final Object key) {
+ throw new UnsupportedOperationException(
+ "Cannot remove entries from an immutable map");
+ }
+
+ @Override
+ public boolean remove(final Object key, final Object value) {
+ throw new UnsupportedOperationException(
+ "Cannot remove entries from an immutable map");
+ }
+
+ @Override
+ public Unit replace(final String key, final Unit value) {
+ throw new UnsupportedOperationException(
+ "Cannot replace entries in an immutable map");
+ }
+
+ @Override
+ public boolean replace(final String key, final Unit oldValue,
+ final Unit newValue) {
+ throw new UnsupportedOperationException(
+ "Cannot replace entries in an immutable map");
+ }
+
+ @Override
+ public void replaceAll(
+ final BiFunction<? super String, ? super Unit, ? extends Unit> function) {
+ throw new UnsupportedOperationException(
+ "Cannot replace entries in an immutable map");
+ }
+
+ @Override
+ public int size() {
+ if (this.units.isEmpty())
+ return 0;
+ else {
+ if (this.prefixes.isEmpty())
+ return this.units.size();
+ else
+ // infinite set
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (this.units.isEmpty() || this.prefixes.isEmpty())
+ return super.toString();
+ else
+ return String.format(
+ "Infinite map of name-unit entries created from units %s and prefixes %s",
+ this.units, this.prefixes);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), this
+ * method ignores prefixes.
+ * </p>
+ */
+ @Override
+ public Collection<Unit> values() {
+ if (this.values == null) {
+ this.values = Collections
+ .unmodifiableCollection(this.units.values());
+ }
+ return this.values;
+ }
+ }
+
+ /**
+ * Replacements done to *all* expression types
+ */
+ private static final Map<Pattern, String> EXPRESSION_REPLACEMENTS = new HashMap<>();
+
+ // add data to expression replacements
+ static {
+ // add spaces around operators
+ for (final String operator : Arrays.asList("\\*", "/", "\\^")) {
+ EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator),
+ " " + operator + " ");
+ }
+
+ // replace multiple spaces with a single space
+ EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " ");
+ // place brackets around any expression of the form "number unit", with or
+ // without the space
+ EXPRESSION_REPLACEMENTS.put(Pattern.compile("((?:-?[1-9]\\d*|0)" // integer
+ + "(?:\\.\\d+(?:[eE]\\d+))?)" // optional decimal point with numbers
+ // after it
+ + "\\s*" // optional space(s)
+ + "([a-zA-Z]+(?:\\^\\d+)?" // any string of letters
+ + "(?:\\s+[a-zA-Z]+(?:\\^\\d+)?))" // optional other letters
+ + "(?!-?\\d)" // no number directly afterwards (avoids matching
+ // "1e3")
+ ), "\\($1 $2\\)");
+ }
+
+ /**
+ * A regular expression that separates names and expressions in unit files.
+ */
+ private static final Pattern NAME_EXPRESSION = Pattern
+ .compile("(\\S+)\\s+(\\S.*)");
+
+ /**
+ * The exponent operator
+ *
+ * @param base base of exponentiation
+ * @param exponentUnit exponent
+ * @return result
+ * @since 2019-04-10
+ * @since v0.2.0
+ */
+ private static final LinearUnit exponentiateUnits(final LinearUnit base,
+ final LinearUnit exponentUnit) {
+ // exponent function - first check if o2 is a number,
+ if (exponentUnit.getBase().equals(SI.ONE.getBase())) {
+ // then check if it is an integer,
+ final double exponent = exponentUnit.getConversionFactor();
+ if (DecimalComparison.equals(exponent % 1, 0))
+ // then exponentiate
+ return base.toExponent((int) (exponent + 0.5));
+ else
+ // not an integer
+ throw new UnsupportedOperationException(
+ "Decimal exponents are currently not supported.");
+ } else
+ // not a number
+ throw new IllegalArgumentException("Exponents must be numbers.");
+ }
+
+ /**
+ * The exponent operator
+ *
+ * @param base base of exponentiation
+ * @param exponentUnit exponent
+ * @return result
+ * @since 2020-08-04
+ */
+ private static final LinearUnitValue exponentiateUnitValues(
+ final LinearUnitValue base, final LinearUnitValue exponentValue) {
+ // exponent function - first check if o2 is a number,
+ if (exponentValue.canConvertTo(SI.ONE)) {
+ // then check if it is an integer,
+ final double exponent = exponentValue.getValueExact();
+ if (DecimalComparison.equals(exponent % 1, 0))
+ // then exponentiate
+ return base.toExponent((int) (exponent + 0.5));
+ else
+ // not an integer
+ throw new UnsupportedOperationException(
+ "Decimal exponents are currently not supported.");
+ } else
+ // not a number
+ throw new IllegalArgumentException("Exponents must be numbers.");
+ }
+
+ /**
+ * The units in this system, excluding prefixes.
+ *
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+ private final Map<String, Unit> prefixlessUnits;
+
+ /**
+ * The unit prefixes in this system.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private final Map<String, UnitPrefix> prefixes;
+
+ /**
+ * The dimensions in this system.
+ *
+ * @since 2019-03-14
+ * @since v0.2.0
+ */
+ private final Map<String, ObjectProduct<BaseDimension>> dimensions;
+
+ /**
+ * A map mapping strings to units (including prefixes)
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final Map<String, Unit> units;
+
+ /**
+ * The rule that specifies when prefix repetition is allowed. It takes in one
+ * argument: a list of the prefixes being applied to the unit
+ * <p>
+ * The prefixes are inputted in <em>application order</em>. This means that
+ * testing whether "kilomegagigametre" is a valid unit is equivalent to
+ * running the following code (assuming all variables are defined correctly):
+ * <br>
+ * {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))}
+ */
+ private Predicate<List<UnitPrefix>> prefixRepetitionRule;
+
+ /**
+ * A parser that can parse unit expressions.
+ *
+ * @since 2019-03-22
+ * @since v0.2.0
+ */
+ private final ExpressionParser<LinearUnit> unitExpressionParser = new ExpressionParser.Builder<>(
+ this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
+ .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
+ .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2)
+ .build();
+
+ /**
+ * A parser that can parse unit value expressions.
+ *
+ * @since 2020-08-04
+ */
+ private final ExpressionParser<LinearUnitValue> unitValueExpressionParser = new ExpressionParser.Builder<>(
+ this::getLinearUnitValue)
+ .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
+ .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
+ .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2)
+ .build();
+
+ /**
+ * A parser that can parse unit prefix expressions
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final ExpressionParser<UnitPrefix> prefixExpressionParser = new ExpressionParser.Builder<>(
+ this::getPrefix).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
+ .addBinaryOperator("^",
+ (o1, o2) -> o1.toExponent(o2.getMultiplier()), 1)
+ .build();
+
+ /**
+ * A parser that can parse unit dimension expressions.
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final ExpressionParser<ObjectProduct<BaseDimension>> unitDimensionParser = new ExpressionParser.Builder<>(
+ this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build();
+
+ /**
+ * Creates the {@code UnitsDatabase}.
+ *
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public UnitDatabase() {
+ this(prefixes -> true);
+ }
+
+ /**
+ * Creates the {@code UnitsDatabase}
+ *
+ * @param prefixRepetitionRule the rule that determines when prefix
+ * repetition is allowed
+ * @since 2020-08-26
+ */
+ public UnitDatabase(Predicate<List<UnitPrefix>> prefixRepetitionRule) {
+ this.prefixlessUnits = new HashMap<>();
+ this.prefixes = new HashMap<>();
+ this.dimensions = new HashMap<>();
+ this.prefixRepetitionRule = prefixRepetitionRule;
+ this.units = ConditionalExistenceCollections.conditionalExistenceMap(
+ new PrefixedUnitMap(this.prefixlessUnits, this.prefixes),
+ entry -> this.prefixRepetitionRule
+ .test(this.getPrefixesFromName(entry.getKey())));
+ }
+
+ /**
+ * Adds a unit dimension to the database.
+ *
+ * @param name dimension's name
+ * @param dimension dimension to add
+ * @throws NullPointerException if name or dimension is null
+ * @since 2019-03-14
+ * @since v0.2.0
+ */
+ public void addDimension(final String name,
+ final ObjectProduct<BaseDimension> dimension) {
+ this.dimensions.put(
+ Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(dimension, "dimension must not be null."));
+ }
+
+ /**
+ * Adds to the list from a line in a unit dimension file.
+ *
+ * @param line line to look at
+ * @param lineCounter number of line, for error messages
+ * @since 2019-04-10
+ * @since v0.2.0
+ */
+ private void addDimensionFromLine(final String line,
+ final long lineCounter) {
+ // ignore lines that start with a # sign - they're comments
+ if (line.isEmpty())
+ return;
+ if (line.contains("#")) {
+ this.addDimensionFromLine(line.substring(0, line.indexOf("#")),
+ lineCounter);
+ return;
+ }
+
+ // divide line into name and expression
+ final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
+ if (!lineMatcher.matches())
+ throw new IllegalArgumentException(String.format(
+ "Error at line %d: Lines of a dimension file must consist of a dimension name, then spaces or tabs, then a dimension expression.",
+ lineCounter));
+ final String name = lineMatcher.group(1);
+ final String expression = lineMatcher.group(2);
+
+ if (name.endsWith(" ")) {
+ System.err.printf("Warning - line %d's dimension name ends in a space",
+ lineCounter);
+ }
+
+ // if expression is "!", search for an existing dimension
+ // if no unit found, throw an error
+ if (expression.equals("!")) {
+ if (!this.containsDimensionName(name))
+ throw new IllegalArgumentException(String.format(
+ "! used but no dimension found (line %d).", lineCounter));
+ } else {
+ // it's a unit, get the unit
+ final ObjectProduct<BaseDimension> dimension;
+ try {
+ dimension = this.getDimensionFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
+ }
+
+ this.addDimension(name, dimension);
+ }
+ }
+
+ /**
+ * Adds a unit prefix to the database.
+ *
+ * @param name prefix's name
+ * @param prefix prefix to add
+ * @throws NullPointerException if name or prefix is null
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public void addPrefix(final String name, final UnitPrefix prefix) {
+ this.prefixes.put(Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(prefix, "prefix must not be null."));
+ }
+
+ /**
+ * Adds a unit to the database.
+ *
+ * @param name unit's name
+ * @param unit unit to add
+ * @throws NullPointerException if unit is null
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public void addUnit(final String name, final Unit unit) {
+ this.prefixlessUnits.put(
+ Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(unit, "unit must not be null."));
+ }
+
+ /**
+ * Adds to the list from a line in a unit file.
+ *
+ * @param line line to look at
+ * @param lineCounter number of line, for error messages
+ * @since 2019-04-10
+ * @since v0.2.0
+ */
+ private void addUnitOrPrefixFromLine(final String line,
+ final long lineCounter) {
+ // ignore lines that start with a # sign - they're comments
+ if (line.isEmpty())
+ return;
+ if (line.contains("#")) {
+ this.addUnitOrPrefixFromLine(line.substring(0, line.indexOf("#")),
+ lineCounter);
+ return;
+ }
+
+ // divide line into name and expression
+ final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
+ if (!lineMatcher.matches())
+ throw new IllegalArgumentException(String.format(
+ "Error at line %d: Lines of a unit file must consist of a unit name, then spaces or tabs, then a unit expression.",
+ lineCounter));
+ final String name = lineMatcher.group(1);
+
+ final String expression = lineMatcher.group(2);
+
+ if (name.endsWith(" ")) {
+ System.err.printf("Warning - line %d's unit name ends in a space",
+ lineCounter);
+ }
+
+ // if expression is "!", search for an existing unit
+ // if no unit found, throw an error
+ if (expression.equals("!")) {
+ if (!this.containsUnitName(name))
+ throw new IllegalArgumentException(String
+ .format("! used but no unit found (line %d).", lineCounter));
+ } else {
+ if (name.endsWith("-")) {
+ final UnitPrefix prefix;
+ try {
+ prefix = this.getPrefixFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
+ }
+ this.addPrefix(name.substring(0, name.length() - 1), prefix);
+ } else {
+ // it's a unit, get the unit
+ final Unit unit;
+ try {
+ unit = this.getUnitFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
+ }
+
+ this.addUnit(name, unit);
+ }
+ }
+ }
+
+ /**
+ * Tests if the database has a unit dimension with this name.
+ *
+ * @param name name to test
+ * @return if database contains name
+ * @since 2019-03-14
+ * @since v0.2.0
+ */
+ public boolean containsDimensionName(final String name) {
+ return this.dimensions.containsKey(name);
+ }
+
+ /**
+ * Tests if the database has a unit prefix with this name.
+ *
+ * @param name name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public boolean containsPrefixName(final String name) {
+ return this.prefixes.containsKey(name);
+ }
+
+ /**
+ * Tests if the database has a unit with this name, taking prefixes into
+ * consideration
+ *
+ * @param name name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public boolean containsUnitName(final String name) {
+ return this.units.containsKey(name);
+ }
+
+ /**
+ * @return a map mapping dimension names to dimensions
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public Map<String, ObjectProduct<BaseDimension>> dimensionMap() {
+ return Collections.unmodifiableMap(this.dimensions);
+ }
+
+ /**
+ * Evaluates a unit expression, following the same rules as
+ * {@link #getUnitFromExpression}.
+ *
+ * @param expression expression to parse
+ * @return {@code LinearUnitValue} representing value of expression
+ * @since 2020-08-04
+ */
+ public LinearUnitValue evaluateUnitExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // attempt to get a unit as an alias, or a number with precision first
+ if (this.containsUnitName(expression))
+ return this.getLinearUnitValue(expression);
+
+ // force operators to have spaces
+ String modifiedExpression = expression;
+ modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
+ modifiedExpression = modifiedExpression.replaceAll("-", " - ");
+
+ // format expression
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
+ }
+
+ // the previous operation breaks negative numbers, fix them!
+ // (i.e. -2 becomes - 2)
+ // FIXME the previous operaton also breaks stuff like "1e-5"
+ for (int i = 0; i < modifiedExpression.length(); i++) {
+ if (modifiedExpression.charAt(i) == '-'
+ && (i < 2 || Arrays.asList('+', '-', '*', '/', '^')
+ .contains(modifiedExpression.charAt(i - 2)))) {
+ // found a broken negative number
+ modifiedExpression = modifiedExpression.substring(0, i + 1)
+ + modifiedExpression.substring(i + 2);
+ }
+ }
+
+ return this.unitValueExpressionParser.parseExpression(modifiedExpression);
+ }
+
+ /**
+ * Gets a unit dimension from the database using its name.
+ *
+ * <p>
+ * This method accepts exponents, like "L^3"
+ * </p>
+ *
+ * @param name dimension's name
+ * @return dimension
+ * @since 2019-03-14
+ * @since v0.2.0
+ */
+ public ObjectProduct<BaseDimension> getDimension(final String name) {
+ Objects.requireNonNull(name, "name must not be null.");
+ if (name.contains("^")) {
+ final String[] baseAndExponent = name.split("\\^");
+
+ final ObjectProduct<BaseDimension> base = this
+ .getDimension(baseAndExponent[0]);
+
+ final int exponent;
+ try {
+ exponent = Integer
+ .parseInt(baseAndExponent[baseAndExponent.length - 1]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponent must be an integer.");
+ }
+
+ return base.toExponent(exponent);
+ }
+ return this.dimensions.get(name);
+ }
+
+ /**
+ * Uses the database's data to parse an expression into a unit dimension
+ * <p>
+ * The expression is a series of any of the following:
+ * <ul>
+ * <li>The name of a unit dimension, which multiplies or divides the result
+ * based on preceding operators</li>
+ * <li>The operators '*' and '/', which multiply and divide (note that just
+ * putting two unit dimensions next to each other is equivalent to
+ * multiplication)</li>
+ * <li>The operator '^' which exponentiates. Exponents must be integers.</li>
+ * </ul>
+ *
+ * @param expression expression to parse
+ * @throws IllegalArgumentException if the expression cannot be parsed
+ * @throws NullPointerException if expression is null
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public ObjectProduct<BaseDimension> getDimensionFromExpression(
+ final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // attempt to get a dimension as an alias first
+ if (this.containsDimensionName(expression))
+ return this.getDimension(expression);
+
+ // force operators to have spaces
+ String modifiedExpression = expression;
+
+ // format expression
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
+ }
+ modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^");
+
+ return this.unitDimensionParser.parseExpression(modifiedExpression);
+ }
+
+ /**
+ * Gets a unit. If it is linear, cast it to a LinearUnit and return it.
+ * Otherwise, throw an {@code IllegalArgumentException}.
+ *
+ * @param name unit's name
+ * @return unit
+ * @since 2019-03-22
+ * @since v0.2.0
+ */
+ private LinearUnit getLinearUnit(final String name) {
+ // see if I am using a function-unit like tempC(100)
+ Objects.requireNonNull(name, "name may not be null");
+ if (name.contains("(") && name.contains(")")) {
+ // break it into function name and value
+ final List<String> parts = Arrays.asList(name.split("\\("));
+ if (parts.size() != 2)
+ throw new IllegalArgumentException(
+ "Format nonlinear units like: unit(value).");
+
+ // solve the function
+ final Unit unit = this.getUnit(parts.get(0));
+ final double value = Double.parseDouble(
+ parts.get(1).substring(0, parts.get(1).length() - 1));
+ return LinearUnit.fromUnitValue(unit, value);
+ } else {
+ // get a linear unit
+ final Unit unit = this.getUnit(name);
+
+ if (unit instanceof LinearUnit)
+ return (LinearUnit) unit;
+ else
+ throw new IllegalArgumentException(
+ String.format("%s is not a linear unit.", name));
+ }
+ }
+
+ /**
+ * Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be
+ * converted to their base units.
+ *
+ * @param name name of unit
+ * @return {@code LinearUnitValue} instance
+ * @since 2020-08-04
+ */
+ private LinearUnitValue getLinearUnitValue(final String name) {
+ try {
+ // try to parse it as a number - otherwise it is not a number!
+ final BigDecimal number = new BigDecimal(name);
+
+ final double uncertainty = Math.pow(10, -number.scale());
+ return LinearUnitValue.of(SI.ONE,
+ UncertainDouble.of(number.doubleValue(), uncertainty));
+ } catch (final NumberFormatException e) {
+ return LinearUnitValue.getExact(this.getLinearUnit(name), 1);
+ }
+ }
+
+ /**
+ * Gets a unit prefix from the database from its name
+ *
+ * @param name prefix's name
+ * @return prefix
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public UnitPrefix getPrefix(final String name) {
+ try {
+ return UnitPrefix.valueOf(Double.parseDouble(name));
+ } catch (final NumberFormatException e) {
+ return this.prefixes.get(name);
+ }
+ }
+
+ /**
+ * Gets all of the prefixes that are on a unit name, in application order.
+ *
+ * @param unitName name of unit
+ * @return prefixes
+ * @since 2020-08-26
+ */
+ List<UnitPrefix> getPrefixesFromName(final String unitName) {
+ final List<UnitPrefix> prefixes = new ArrayList<>();
+ String name = unitName;
+
+ while (!this.prefixlessUnits.containsKey(name)) {
+ // find the longest prefix
+ String longestPrefixName = null;
+ int longestLength = name.length();
+
+ while (longestPrefixName == null) {
+ longestLength--;
+ if (longestLength <= 0)
+ throw new AssertionError(
+ "No prefix found in " + name + ", but it is not a unit!");
+ if (this.prefixes.containsKey(name.substring(0, longestLength))) {
+ longestPrefixName = name.substring(0, longestLength);
+ }
+ }
+
+ // longest prefix found!
+ final UnitPrefix prefix = this.getPrefix(longestPrefixName);
+ prefixes.add(0, prefix);
+ name = name.substring(longestLength);
+ }
+ return prefixes;
+ }
+
+ /**
+ * Gets a unit prefix from a prefix expression
+ * <p>
+ * Currently, prefix expressions are much simpler than unit expressions: They
+ * are either a number or the name of another prefix
+ * </p>
+ *
+ * @param expression expression to input
+ * @return prefix
+ * @throws IllegalArgumentException if expression cannot be parsed
+ * @throws NullPointerException if any argument is null
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public UnitPrefix getPrefixFromExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // attempt to get a unit as an alias first
+ if (this.containsUnitName(expression))
+ return this.getPrefix(expression);
+
+ // force operators to have spaces
+ String modifiedExpression = expression;
+
+ // format expression
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
+ }
+
+ return this.prefixExpressionParser.parseExpression(modifiedExpression);
+ }
+
+ /**
+ * @return the prefixRepetitionRule
+ * @since 2020-08-26
+ */
+ public final Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
+ return this.prefixRepetitionRule;
+ }
+
+ /**
+ * Gets a unit from the database from its name, looking for prefixes.
+ *
+ * @param name unit's name
+ * @return unit
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public Unit getUnit(final String name) {
+ try {
+ final double value = Double.parseDouble(name);
+ return SI.ONE.times(value);
+ } catch (final NumberFormatException e) {
+ final Unit unit = this.units.get(name);
+ if (unit == null)
+ throw new NoSuchElementException("No unit " + name);
+ else if (unit.getPrimaryName().isEmpty())
+ return unit.withName(NameSymbol.ofName(name));
+ else if (!unit.getPrimaryName().get().equals(name)) {
+ final Set<String> otherNames = new HashSet<>(unit.getOtherNames());
+ otherNames.add(unit.getPrimaryName().get());
+ return unit.withName(NameSymbol.ofNullable(name,
+ unit.getSymbol().orElse(null), otherNames));
+ } else if (!unit.getOtherNames().contains(name)) {
+ final Set<String> otherNames = new HashSet<>(unit.getOtherNames());
+ otherNames.add(name);
+ return unit.withName(
+ NameSymbol.ofNullable(unit.getPrimaryName().orElse(null),
+ unit.getSymbol().orElse(null), otherNames));
+ } else
+ return unit;
+ }
+
+ }
+
+ /**
+ * Uses the database's unit data to parse an expression into a unit
+ * <p>
+ * The expression is a series of any of the following:
+ * <ul>
+ * <li>The name of a unit, which multiplies or divides the result based on
+ * preceding operators</li>
+ * <li>The operators '*' and '/', which multiply and divide (note that just
+ * putting two units or values next to each other is equivalent to
+ * multiplication)</li>
+ * <li>The operator '^' which exponentiates. Exponents must be integers.</li>
+ * <li>A number which is multiplied or divided</li>
+ * </ul>
+ * This method only works with linear units.
+ *
+ * @param expression expression to parse
+ * @throws IllegalArgumentException if the expression cannot be parsed
+ * @throws NullPointerException if expression is null
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+ public Unit getUnitFromExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // attempt to get a unit as an alias first
+ if (this.containsUnitName(expression))
+ return this.getUnit(expression);
+
+ // force operators to have spaces
+ String modifiedExpression = expression;
+ modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
+ modifiedExpression = modifiedExpression.replaceAll("-", " - ");
+
+ // format expression
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
+ }
+
+ // the previous operation breaks negative numbers, fix them!
+ // (i.e. -2 becomes - 2)
+ for (int i = 0; i < modifiedExpression.length(); i++) {
+ if (modifiedExpression.charAt(i) == '-'
+ && (i < 2 || Arrays.asList('+', '-', '*', '/', '^')
+ .contains(modifiedExpression.charAt(i - 2)))) {
+ // found a broken negative number
+ modifiedExpression = modifiedExpression.substring(0, i + 1)
+ + modifiedExpression.substring(i + 2);
+ }
+ }
+
+ return this.unitExpressionParser.parseExpression(modifiedExpression);
+ }
+
+ /**
+ * Adds all dimensions from a file, using data from the database to parse
+ * them.
+ * <p>
+ * Each line in the file should consist of a name and an expression (parsed
+ * by getDimensionFromExpression) separated by any number of tab characters.
+ * <p>
+ * <p>
+ * Allowed exceptions:
+ * <ul>
+ * <li>Anything after a '#' character is considered a comment and
+ * ignored.</li>
+ * <li>Blank lines are also ignored</li>
+ * <li>If an expression consists of a single exclamation point, instead of
+ * parsing it, this method will search the database for an existing unit. If
+ * no unit is found, an IllegalArgumentException is thrown. This is used to
+ * define initial units and ensure that the database contains them.</li>
+ * </ul>
+ *
+ * @param file file to read
+ * @throws IllegalArgumentException if the file cannot be parsed, found or
+ * read
+ * @throws NullPointerException if file is null
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public void loadDimensionFile(final Path file) {
+ Objects.requireNonNull(file, "file must not be null.");
+ try {
+ long lineCounter = 0;
+ for (final String line : Files.readAllLines(file)) {
+ this.addDimensionFromLine(line, ++lineCounter);
+ }
+ } catch (final FileNotFoundException e) {
+ throw new IllegalArgumentException("Could not find file " + file, e);
+ } catch (final IOException e) {
+ throw new IllegalArgumentException("Could not read file " + file, e);
+ }
+ }
+
+ /**
+ * Adds all units from a file, using data from the database to parse them.
+ * <p>
+ * Each line in the file should consist of a name and an expression (parsed
+ * by getUnitFromExpression) separated by any number of tab characters.
+ * <p>
+ * <p>
+ * Allowed exceptions:
+ * <ul>
+ * <li>Anything after a '#' character is considered a comment and
+ * ignored.</li>
+ * <li>Blank lines are also ignored</li>
+ * <li>If an expression consists of a single exclamation point, instead of
+ * parsing it, this method will search the database for an existing unit. If
+ * no unit is found, an IllegalArgumentException is thrown. This is used to
+ * define initial units and ensure that the database contains them.</li>
+ * </ul>
+ *
+ * @param file file to read
+ * @throws IllegalArgumentException if the file cannot be parsed, found or
+ * read
+ * @throws NullPointerException if file is null
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public void loadUnitsFile(final Path file) {
+ Objects.requireNonNull(file, "file must not be null.");
+ try {
+ long lineCounter = 0;
+ for (final String line : Files.readAllLines(file)) {
+ this.addUnitOrPrefixFromLine(line, ++lineCounter);
+ }
+ } catch (final FileNotFoundException e) {
+ throw new IllegalArgumentException("Could not find file " + file, e);
+ } catch (final IOException e) {
+ throw new IllegalArgumentException("Could not read file " + file, e);
+ }
+ }
+
+ /**
+ * @return a map mapping prefix names to prefixes
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public Map<String, UnitPrefix> prefixMap() {
+ return Collections.unmodifiableMap(this.prefixes);
+ }
+
+ /**
+ * @param prefixRepetitionRule the prefixRepetitionRule to set
+ * @since 2020-08-26
+ */
+ public final void setPrefixRepetitionRule(
+ Predicate<List<UnitPrefix>> prefixRepetitionRule) {
+ this.prefixRepetitionRule = prefixRepetitionRule;
+ }
+
+ /**
+ * @return a string stating the number of units, prefixes and dimensions in
+ * the database
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ "Unit Database with %d units, %d unit prefixes and %d dimensions",
+ this.prefixlessUnits.size(), this.prefixes.size(),
+ this.dimensions.size());
+ }
+
+ /**
+ * Returns a map mapping unit names to units, including units with prefixes.
+ * <p>
+ * The returned map is infinite in size if there is at least one unit and at
+ * least one prefix. If it is infinite, some operations that only work with
+ * finite collections, like converting name/entry sets to arrays, will throw
+ * an {@code IllegalStateException}.
+ * </p>
+ * <p>
+ * Specifically, the operations that will throw an IllegalStateException if
+ * the map is infinite in size are:
+ * <ul>
+ * <li>{@code unitMap.entrySet().toArray()} (either overloading)</li>
+ * <li>{@code unitMap.keySet().toArray()} (either overloading)</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), the map's
+ * {@link PrefixedUnitMap#containsValue containsValue} and
+ * {@link PrefixedUnitMap#values() values()} methods currently ignore
+ * prefixes.
+ * </p>
+ *
+ * @return a map mapping unit names to units, including prefixed names
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public Map<String, Unit> unitMap() {
+ return this.units; // PrefixedUnitMap is immutable so I don't need to make
+ // an unmodifiable map.
+ }
+
+ /**
+ * @return a map mapping unit names to units, ignoring prefixes
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public Map<String, Unit> unitMapPrefixless() {
+ return Collections.unmodifiableMap(this.prefixlessUnits);
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/UnitPrefix.java b/src/main/java/org/unitConverter/unit/UnitPrefix.java
new file mode 100644
index 0000000..31cc0b3
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/UnitPrefix.java
@@ -0,0 +1,242 @@
+/**
+ * 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;
+import java.util.Optional;
+import java.util.Set;
+
+import org.unitConverter.math.DecimalComparison;
+
+/**
+ * A prefix that can be applied to a {@code LinearUnit} to multiply it by some value
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class UnitPrefix {
+ /**
+ * Gets a {@code UnitPrefix} from a multiplier
+ *
+ * @param multiplier
+ * multiplier of prefix
+ * @return prefix
+ * @since 2019-10-16
+ */
+ public static UnitPrefix valueOf(final double multiplier) {
+ return new UnitPrefix(multiplier, NameSymbol.EMPTY);
+ }
+
+ /**
+ * Gets a {@code UnitPrefix} from a multiplier and a name
+ *
+ * @param multiplier
+ * multiplier of prefix
+ * @param ns
+ * name(s) and symbol of prefix
+ * @return prefix
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if ns is null
+ */
+ public static UnitPrefix valueOf(final double multiplier, final NameSymbol ns) {
+ return new UnitPrefix(multiplier, Objects.requireNonNull(ns, "ns must not be null."));
+ }
+
+ /**
+ * This prefix's primary name
+ */
+ private final Optional<String> primaryName;
+
+ /**
+ * This prefix's symbol
+ */
+ private final Optional<String> symbol;
+
+ /**
+ * Other names and symbols used by this prefix
+ */
+ private final Set<String> otherNames;
+
+ /**
+ * The number that this prefix multiplies units by
+ *
+ * @since 2019-10-16
+ */
+ private final double multiplier;
+
+ /**
+ * Creates the {@code DefaultUnitPrefix}.
+ *
+ * @param multiplier
+ * @since 2019-01-14
+ * @since v0.2.0
+ */
+ private UnitPrefix(final double multiplier, final NameSymbol ns) {
+ this.multiplier = multiplier;
+ this.primaryName = ns.getPrimaryName();
+ this.symbol = ns.getSymbol();
+ this.otherNames = ns.getOtherNames();
+ }
+
+ /**
+ * Divides this prefix by a scalar
+ *
+ * @param divisor
+ * number to divide by
+ * @return quotient of prefix and scalar
+ * @since 2019-10-16
+ */
+ public UnitPrefix dividedBy(final double divisor) {
+ return valueOf(this.getMultiplier() / divisor);
+ }
+
+ /**
+ * Divides this prefix by {@code other}.
+ *
+ * @param other
+ * prefix to divide by
+ * @return quotient of prefixes
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public UnitPrefix dividedBy(final UnitPrefix other) {
+ return valueOf(this.getMultiplier() / other.getMultiplier());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Uses the prefix's multiplier to determine equality.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof UnitPrefix))
+ return false;
+ final UnitPrefix other = (UnitPrefix) obj;
+ return DecimalComparison.equals(this.getMultiplier(), other.getMultiplier());
+ }
+
+ /**
+ * @return prefix's multiplier
+ * @since 2019-11-26
+ */
+ public double getMultiplier() {
+ return this.multiplier;
+ }
+
+ /**
+ * @return other names
+ * @since 2019-11-26
+ */
+ public final Set<String> getOtherNames() {
+ return this.otherNames;
+ }
+
+ /**
+ * @return primary name
+ * @since 2019-11-26
+ */
+ public final Optional<String> getPrimaryName() {
+ return this.primaryName;
+ }
+
+ /**
+ * @return symbol
+ * @since 2019-11-26
+ */
+ public final Optional<String> getSymbol() {
+ return this.symbol;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Uses the prefix's multiplier to determine a hash code.
+ */
+ @Override
+ public int hashCode() {
+ return DecimalComparison.hash(this.getMultiplier());
+ }
+
+ /**
+ * Multiplies this prefix by a scalar
+ *
+ * @param multiplicand
+ * number to multiply by
+ * @return product of prefix and scalar
+ * @since 2019-10-16
+ */
+ public UnitPrefix times(final double multiplicand) {
+ return valueOf(this.getMultiplier() * multiplicand);
+ }
+
+ /**
+ * Multiplies this prefix by {@code other}.
+ *
+ * @param other
+ * prefix to multiply by
+ * @return product of prefixes
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public UnitPrefix times(final UnitPrefix other) {
+ return valueOf(this.getMultiplier() * other.getMultiplier());
+ }
+
+ /**
+ * Raises this prefix to an exponent.
+ *
+ * @param exponent
+ * exponent to raise to
+ * @return result of exponentiation.
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public UnitPrefix toExponent(final double exponent) {
+ return valueOf(Math.pow(this.getMultiplier(), exponent));
+ }
+
+ /**
+ * @return a string describing the prefix and its multiplier
+ */
+ @Override
+ public String toString() {
+ if (this.primaryName.isPresent())
+ return String.format("%s (\u00D7 %s)", this.primaryName.get(), this.multiplier);
+ else if (this.symbol.isPresent())
+ return String.format("%s (\u00D7 %s)", this.symbol.get(), this.multiplier);
+ else
+ return String.format("Unit Prefix (\u00D7 %s)", this.multiplier);
+ }
+
+ /**
+ * @param ns
+ * name(s) and symbol to use
+ * @return copy of this prefix with provided name(s) and symbol
+ * @since 2019-11-26
+ * @throws NullPointerException
+ * if ns is null
+ */
+ public UnitPrefix withName(final NameSymbol ns) {
+ return valueOf(this.multiplier, ns);
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/UnitValue.java b/src/main/java/org/unitConverter/unit/UnitValue.java
new file mode 100644
index 0000000..c138332
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/UnitValue.java
@@ -0,0 +1,172 @@
+/**
+ * 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;
+import java.util.Optional;
+
+/**
+ * A value expressed in a unit.
+ *
+ * Unless otherwise indicated, all methods in this class throw a
+ * {@code NullPointerException} when an argument is null.
+ *
+ * @author Adrien Hopkins
+ * @since 2020-07-26
+ */
+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);
+ }
+
+ 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) {
+ this.unit = unit;
+ 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.unit.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.unit.canConvertTo(other);
+ }
+
+ /**
+ * Returns a UnitlikeValue that represents the same value expressed in a
+ * different unitlike form.
+ *
+ * @param other new unit to express value in
+ * @return value expressed in {@code other}
+ */
+ public final <U extends Unitlike<W>, W> UnitlikeValue<U, W> convertTo(
+ U other) {
+ return UnitlikeValue.of(other,
+ this.unit.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 UnitValue convertTo(Unit other) {
+ return UnitValue.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.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof UnitValue))
+ return false;
+ final UnitValue other = (UnitValue) obj;
+ 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.getUnit().getBase(),
+ this.getUnit().convertFromBase(this.getValue()));
+ }
+
+ @Override
+ public String toString() {
+ final Optional<String> primaryName = this.getUnit().getPrimaryName();
+ final Optional<String> symbol = this.getUnit().getSymbol();
+ if (primaryName.isEmpty() && symbol.isEmpty()) {
+ final double baseValue = this.getUnit().convertToBase(this.getValue());
+ return String.format("%s unnamed unit (= %s %s)", this.getValue(),
+ baseValue, this.getUnit().getBase());
+ } else {
+ final String unitName = symbol.orElse(primaryName.get());
+ return this.getValue() + " " + unitName;
+ }
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/Unitlike.java b/src/main/java/org/unitConverter/unit/Unitlike.java
new file mode 100644
index 0000000..8077771
--- /dev/null
+++ b/src/main/java/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
+ */
+ protected Unitlike(ObjectProduct<BaseUnit> unitBase, NameSymbol ns) {
+ this.unitBase = Objects.requireNonNull(unitBase,
+ "unitBase may not be null");
+ this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null");
+ }
+
+ /**
+ * 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/main/java/org/unitConverter/unit/UnitlikeValue.java b/src/main/java/org/unitConverter/unit/UnitlikeValue.java
new file mode 100644
index 0000000..79201c4
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/UnitlikeValue.java
@@ -0,0 +1,174 @@
+/**
+ * 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<T extends Unitlike<V>, V> {
+ /**
+ * Gets a {@code UnitlikeValue<V>}.
+ *
+ * @since 2020-10-02
+ */
+ public static <T extends Unitlike<V>, V> UnitlikeValue<T, V> of(T unitlike,
+ V value) {
+ return new UnitlikeValue<>(unitlike, value);
+ }
+
+ private final T unitlike;
+ private final V value;
+
+ /**
+ * @param unitlike
+ * @param value
+ * @since 2020-09-07
+ */
+ private UnitlikeValue(T 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 UnitlikeValue that represents the same value expressed in a
+ * different unitlike form.
+ *
+ * @param other new unit to express value in
+ * @return value expressed in {@code other}
+ */
+ public final <U extends Unitlike<W>, W> UnitlikeValue<U, W> convertTo(
+ U other) {
+ return UnitlikeValue.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 UnitValue convertTo(Unit other) {
+ return UnitValue.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;
+ }
+ }
+}
diff --git a/src/main/java/org/unitConverter/unit/package-info.java b/src/main/java/org/unitConverter/unit/package-info.java
new file mode 100644
index 0000000..2f0e097
--- /dev/null
+++ b/src/main/java/org/unitConverter/unit/package-info.java
@@ -0,0 +1,24 @@
+/**
+ * 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/>.
+ */
+/**
+ * Everything to do with the units that make up Unit Converter.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ * @since v0.1.0
+ */
+package org.unitConverter.unit; \ No newline at end of file