From 69f2849d25a41ae7c0383636557deda1bc64247d Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 16 Oct 2019 12:29:19 -0400
Subject: Created more generalized objects for use in the new units.
---
src/org/unitConverter/math/ObjectProduct.java | 261 +++++++++++++++++++++
src/org/unitConverter/math/ObjectProductTest.java | 79 +++++++
src/org/unitConverter/math/StandardDimensions.java | 86 +++++++
src/org/unitConverter/math/ZeroIsNullMap.java | 129 ++++++++++
src/org/unitConverter/math/ZeroIsNullMapTest.java | 113 +++++++++
5 files changed, 668 insertions(+)
create mode 100644 src/org/unitConverter/math/ObjectProduct.java
create mode 100644 src/org/unitConverter/math/ObjectProductTest.java
create mode 100644 src/org/unitConverter/math/StandardDimensions.java
create mode 100644 src/org/unitConverter/math/ZeroIsNullMap.java
create mode 100644 src/org/unitConverter/math/ZeroIsNullMapTest.java
diff --git a/src/org/unitConverter/math/ObjectProduct.java b/src/org/unitConverter/math/ObjectProduct.java
new file mode 100644
index 0000000..ec4d2d6
--- /dev/null
+++ b/src/org/unitConverter/math/ObjectProduct.java
@@ -0,0 +1,261 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.math;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * An immutable product of multiple objects of a type, such as base units. The objects can be multiplied and
+ * exponentiated.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class ObjectProduct {
+ /**
+ * Returns an empty ObjectProduct of a certain type
+ *
+ * @param
+ * type of objects that can be multiplied
+ * @return empty product
+ * @since 2019-10-16
+ */
+ public static final ObjectProduct empty() {
+ return new ObjectProduct<>(new HashMap<>());
+ }
+
+ /**
+ * Gets an {@code ObjectProduct} from an object-to-integer mapping
+ *
+ * @param
+ * type of object in product
+ * @param map
+ * map mapping objects to exponents
+ * @return object product
+ * @since 2019-10-16
+ */
+ public static final ObjectProduct fromExponentMapping(final Map map) {
+ return new ObjectProduct<>(new HashMap<>(map));
+ }
+
+ /**
+ * Gets an ObjectProduct that has one of the inputted argument, and nothing else.
+ *
+ * @param object
+ * object that will be in the product
+ * @return product
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if object is null
+ */
+ public static final ObjectProduct oneOf(final T object) {
+ Objects.requireNonNull(object, "object must not be null.");
+ final Map map = new HashMap<>();
+ map.put(object, 1);
+ return new ObjectProduct<>(map);
+ }
+
+ /**
+ * The objects that make up the product, mapped to their exponents. This map treats zero as null, and is immutable.
+ *
+ * @since 2019-10-16
+ */
+ final Map exponents;
+
+ /**
+ * Creates the {@code ObjectProduct}.
+ *
+ * @param exponents
+ * objects that make up this product
+ * @since 2019-10-16
+ */
+ private ObjectProduct(final Map exponents) {
+ this.exponents = Collections.unmodifiableMap(ZeroIsNullMap.create(new HashMap<>(exponents)));
+ }
+
+ /**
+ * Calculates the quotient of two products
+ *
+ * @param other
+ * other product
+ * @return quotient of two products
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if other is null
+ */
+ public ObjectProduct dividedBy(final ObjectProduct other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ // get a list of all objects in both sets
+ final Set objects = new HashSet<>();
+ objects.addAll(this.getBaseSet());
+ objects.addAll(other.getBaseSet());
+
+ // get a list of all exponents
+ final Map map = new HashMap<>(objects.size());
+ for (final T key : objects) {
+ map.put(key, this.getExponent(key) - other.getExponent(key));
+ }
+
+ // create the product
+ return new ObjectProduct<>(map);
+ }
+
+ // this method relies on the use of ZeroIsNullMap
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof ObjectProduct))
+ return false;
+ final ObjectProduct> other = (ObjectProduct>) obj;
+ return Objects.equals(this.exponents, other.exponents);
+ }
+
+ /**
+ * @return immutable map mapping objects to exponents
+ * @since 2019-10-16
+ */
+ public Map exponentMap() {
+ return this.exponents;
+ }
+
+ /**
+ * @return a set of all of the base objects with non-zero exponents that make up this dimension.
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ public final Set getBaseSet() {
+ final Set dimensions = new HashSet<>();
+
+ // add all dimensions with a nonzero exponent - zero exponents shouldn't be there in the first place
+ for (final T dimension : this.exponents.keySet()) {
+ if (!this.exponents.get(dimension).equals(0)) {
+ dimensions.add(dimension);
+ }
+ }
+
+ return dimensions;
+ }
+
+ /**
+ * Gets the exponent for a specific dimension.
+ *
+ * @param dimension
+ * dimension to check
+ * @return exponent for that dimension
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ public int getExponent(final T dimension) {
+ return this.exponents.getOrDefault(dimension, 0);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.exponents);
+ }
+
+ /**
+ * @return true if this product is a "base", i.e. it has one exponent of one and no other nonzero exponents
+ * @since 2019-10-16
+ */
+ public boolean isBase() {
+ int oneCount = 0;
+ boolean twoOrMore = false; // has exponents of 2 or more
+ for (final T b : this.getBaseSet()) {
+ if (this.getExponent(b) == 1) {
+ oneCount++;
+ } else if (this.getExponent(b) != 0) {
+ twoOrMore = true;
+ }
+ }
+ return oneCount == 1 && !twoOrMore;
+ }
+
+ /**
+ * Multiplies this product by another
+ *
+ * @param other
+ * other product
+ * @return product of two products
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if other is null
+ */
+ public ObjectProduct times(final ObjectProduct other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ // get a list of all objects in both sets
+ final Set objects = new HashSet<>();
+ objects.addAll(this.getBaseSet());
+ objects.addAll(other.getBaseSet());
+
+ // get a list of all exponents
+ final Map map = new HashMap<>(objects.size());
+ for (final T key : objects) {
+ map.put(key, this.getExponent(key) + other.getExponent(key));
+ }
+
+ // create the product
+ return new ObjectProduct<>(map);
+ }
+
+ /**
+ * Returns this product, but to an exponent
+ *
+ * @param exponent
+ * exponent
+ * @return result of exponentiation
+ * @since 2019-10-16
+ */
+ public ObjectProduct toExponent(final int exponent) {
+ final Map map = new HashMap<>(this.exponents);
+ for (final T key : this.exponents.keySet()) {
+ map.put(key, this.getExponent(key) * exponent);
+ }
+ return new ObjectProduct<>(map);
+ }
+
+ @Override
+ public String toString() {
+ final List positiveStringComponents = new ArrayList<>();
+ final List negativeStringComponents = new ArrayList<>();
+
+ // for each base dimension that makes up this dimension, add it and its exponent
+ for (final T dimension : this.getBaseSet()) {
+ final int exponent = this.exponents.get(dimension);
+ if (exponent > 0) {
+ positiveStringComponents.add(String.format("%s^%d", dimension, exponent));
+ } else if (exponent < 0) {
+ negativeStringComponents.add(String.format("%s^%d", dimension, -exponent));
+ }
+ }
+
+ final String positiveString = positiveStringComponents.isEmpty() ? "1"
+ : String.join(" * ", positiveStringComponents);
+ final String negativeString = negativeStringComponents.isEmpty() ? ""
+ : " / " + String.join(" * ", negativeStringComponents);
+
+ return positiveString + negativeString;
+ }
+}
diff --git a/src/org/unitConverter/math/ObjectProductTest.java b/src/org/unitConverter/math/ObjectProductTest.java
new file mode 100644
index 0000000..03c767c
--- /dev/null
+++ b/src/org/unitConverter/math/ObjectProductTest.java
@@ -0,0 +1,79 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.math;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.unitConverter.dimension.StandardDimensions.AREA;
+import static org.unitConverter.dimension.StandardDimensions.ENERGY;
+import static org.unitConverter.dimension.StandardDimensions.LENGTH;
+import static org.unitConverter.dimension.StandardDimensions.MASS;
+import static org.unitConverter.dimension.StandardDimensions.MASS_DENSITY;
+import static org.unitConverter.dimension.StandardDimensions.QUANTITY;
+import static org.unitConverter.dimension.StandardDimensions.TIME;
+import static org.unitConverter.dimension.StandardDimensions.VOLUME;
+
+import org.junit.jupiter.api.Test;
+import org.unitConverter.dimension.SIBaseDimension;
+import org.unitConverter.dimension.UnitDimension;
+
+/**
+ * Tests for {@link ObjectProduct} using BaseDimension as a test object. This is NOT part of this program's public API.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+class ObjectProductTest {
+ /**
+ * Tests {@link UnitDimension#equals}
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ @Test
+ public void testEquals() {
+ assertEquals(LENGTH, LENGTH);
+ assertFalse(LENGTH.equals(QUANTITY));
+ }
+
+ /**
+ * Tests {@code UnitDimension}'s exponentiation
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ @Test
+ public void testExponents() {
+ assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
+ assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
+ }
+
+ /**
+ * Tests {@code UnitDimension}'s multiplication and division.
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ @Test
+ public void testMultiplicationAndDivision() {
+ assertEquals(AREA, LENGTH.times(LENGTH));
+ assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME));
+ assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME));
+ assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME));
+ }
+}
diff --git a/src/org/unitConverter/math/StandardDimensions.java b/src/org/unitConverter/math/StandardDimensions.java
new file mode 100644
index 0000000..db5efc3
--- /dev/null
+++ b/src/org/unitConverter/math/StandardDimensions.java
@@ -0,0 +1,86 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.math;
+
+import org.unitConverter.dimension.BaseDimension;
+import org.unitConverter.dimension.OtherBaseDimension;
+import org.unitConverter.dimension.SIBaseDimension;
+
+/**
+ * All of the dimensions that are used by the SI. (Test data for the ObjectProductTest)
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+final class StandardDimensions {
+ // base dimensions
+ public static final ObjectProduct EMPTY = ObjectProduct.empty();
+ public static final ObjectProduct LENGTH = ObjectProduct.oneOf(SIBaseDimension.LENGTH);
+ public static final ObjectProduct MASS = ObjectProduct.oneOf(SIBaseDimension.MASS);
+ public static final ObjectProduct TIME = ObjectProduct.oneOf(SIBaseDimension.TIME);
+ public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct
+ .oneOf(SIBaseDimension.ELECTRIC_CURRENT);
+ public static final ObjectProduct TEMPERATURE = ObjectProduct.oneOf(SIBaseDimension.TEMPERATURE);
+ public static final ObjectProduct QUANTITY = ObjectProduct.oneOf(SIBaseDimension.QUANTITY);
+ public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct
+ .oneOf(SIBaseDimension.LUMINOUS_INTENSITY);
+ public static final ObjectProduct INFORMATION = ObjectProduct.oneOf(OtherBaseDimension.INFORMATION);
+ public static final ObjectProduct CURRENCY = ObjectProduct.oneOf(OtherBaseDimension.CURRENCY);
+ // derived dimensions without named SI units
+ public static final ObjectProduct AREA = LENGTH.times(LENGTH);
+
+ public static final ObjectProduct VOLUME = AREA.times(LENGTH);
+ public static final ObjectProduct VELOCITY = LENGTH.dividedBy(TIME);
+ public static final ObjectProduct ACCELERATION = VELOCITY.dividedBy(TIME);
+ public static final ObjectProduct WAVENUMBER = EMPTY.dividedBy(LENGTH);
+ public static final ObjectProduct MASS_DENSITY = MASS.dividedBy(VOLUME);
+ public static final ObjectProduct SURFACE_DENSITY = MASS.dividedBy(AREA);
+ public static final ObjectProduct SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
+ public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
+ public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
+ public static final ObjectProduct CONCENTRATION = QUANTITY.dividedBy(VOLUME);
+ public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION.times(MASS);
+ public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
+ public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
+ public static final ObjectProduct REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
+ public static final ObjectProduct ANGLE = LENGTH.dividedBy(LENGTH);
+ public static final ObjectProduct SOLID_ANGLE = AREA.dividedBy(AREA);
+ // derived dimensions with named SI units
+ public static final ObjectProduct FREQUENCY = EMPTY.dividedBy(TIME);
+
+ public static final ObjectProduct FORCE = MASS.times(ACCELERATION);
+ public static final ObjectProduct ENERGY = FORCE.times(LENGTH);
+ public static final ObjectProduct POWER = ENERGY.dividedBy(TIME);
+ public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
+ public static final ObjectProduct VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
+ public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
+ public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
+ public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE.times(TIME);
+ public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
+ public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
+ public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
+ public static final ObjectProduct SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
+ public static final ObjectProduct CATALYTIC_ACTIVITY = QUANTITY.dividedBy(TIME);
+
+ // You may NOT get StandardDimensions instances!
+ private StandardDimensions() {
+ throw new AssertionError();
+ }
+}
diff --git a/src/org/unitConverter/math/ZeroIsNullMap.java b/src/org/unitConverter/math/ZeroIsNullMap.java
new file mode 100644
index 0000000..10e4d52
--- /dev/null
+++ b/src/org/unitConverter/math/ZeroIsNullMap.java
@@ -0,0 +1,129 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.math;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A "wrapper" for an existing map that treats a zero value as equivalent to null.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ * @param
+ * type of key
+ */
+public final class ZeroIsNullMap extends AbstractMap {
+ /**
+ * Create a ZeroIsNullMap. This method always creates a new map.
+ *
+ * @param
+ * type of key
+ * @param map
+ * map to input
+ * @return map that treats zero as null
+ * @throws NullPointerException
+ * if map is null
+ * @since 2019-10-16
+ */
+ public static Map create(final Map map) {
+ return new ZeroIsNullMap<>(Objects.requireNonNull(map, "map must not be null."));
+ }
+
+ private final Map map;
+
+ /**
+ * Creates the {@code ObjectProductMap}.
+ *
+ * @param map
+ * @since 2019-10-16
+ */
+ private ZeroIsNullMap(final Map map) {
+ this.map = map;
+ }
+
+ @Override
+ public void clear() {
+ this.map.clear();
+ }
+
+ @Override
+ public boolean containsKey(final Object key) {
+ return this.map.containsKey(key) && this.map.get(key) != 0;
+ }
+
+ @Override
+ public boolean containsValue(final Object value) {
+ return this.values().contains(value);
+ }
+
+ @Override
+ public Set> entrySet() {
+ final Set> entrySet = new HashSet<>(this.map.entrySet());
+ entrySet.removeIf(e -> e.getValue() == 0);
+ return entrySet;
+ }
+
+ @Override
+ public Integer get(final Object key) {
+ final Integer i = this.map.get(key);
+ if (Objects.equals(i, 0))
+ return null;
+ else
+ return i;
+ }
+
+ @Override
+ public Set keySet() {
+ final Set keySet = new HashSet<>(this.map.keySet());
+ keySet.removeIf(k -> this.map.get(k) == 0);
+ return keySet;
+ }
+
+ @Override
+ public Integer put(final T key, final Integer value) {
+ if (value != 0)
+ return this.map.put(key, value);
+ else
+ return null;
+ }
+
+ @Override
+ public void putAll(final Map extends T, ? extends Integer> m) {
+ for (final T key : m.keySet()) {
+ this.put(key, m.get(key));
+ }
+ }
+
+ @Override
+ public Integer remove(final Object key) {
+ return this.map.remove(key);
+ }
+
+ @Override
+ public Collection values() {
+ final List values = new ArrayList<>(this.map.values());
+ values.removeIf(i -> i == 0);
+ return values;
+ }
+}
\ No newline at end of file
diff --git a/src/org/unitConverter/math/ZeroIsNullMapTest.java b/src/org/unitConverter/math/ZeroIsNullMapTest.java
new file mode 100644
index 0000000..c3db951
--- /dev/null
+++ b/src/org/unitConverter/math/ZeroIsNullMapTest.java
@@ -0,0 +1,113 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.math;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+class ZeroIsNullMapTest {
+
+ /**
+ * @return map to be used for test data
+ * @since 2019-10-16
+ */
+ Map getTestMap() {
+ final Map map = new HashMap<>();
+ map.put("one", 1);
+ map.put("two", 2);
+ map.put("zero", 0);
+ map.put("ten", 10);
+ return ZeroIsNullMap.create(map);
+ }
+
+ /**
+ * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsKey(java.lang.Object)}.
+ */
+ @Test
+ void testContainsKeyObject() {
+ final Map map = this.getTestMap();
+ assertTrue(map.containsKey("one"));
+ assertTrue(map.containsKey("ten"));
+ assertFalse(map.containsKey("five"));
+ assertFalse(map.containsKey("zero"));
+ }
+
+ /**
+ * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsValue(java.lang.Object)}.
+ */
+ @Test
+ void testContainsValueObject() {
+ final Map map = this.getTestMap();
+ assertTrue(map.containsValue(1));
+ assertTrue(map.containsValue(10));
+ assertFalse(map.containsValue(5));
+ assertFalse(map.containsValue(0));
+ }
+
+ /**
+ * Test method for {@link org.unitConverter.math.ZeroIsNullMap#entrySet()}.
+ */
+ @Test
+ void testEntrySet() {
+ final Map map = this.getTestMap();
+ for (final Entry e : map.entrySet()) {
+ assertTrue(e.getValue() != 0);
+ }
+ }
+
+ /**
+ * Test method for {@link org.unitConverter.math.ZeroIsNullMap#get(java.lang.Object)}.
+ */
+ @Test
+ void testGetObject() {
+ final Map map = this.getTestMap();
+ assertEquals(1, map.get("one"));
+ assertEquals(10, map.get("ten"));
+ assertEquals(null, map.get("five"));
+ assertEquals(null, map.get("zero"));
+ }
+
+ /**
+ * Test method for {@link org.unitConverter.math.ZeroIsNullMap#keySet()}.
+ */
+ @Test
+ void testKeySet() {
+ final Map map = this.getTestMap();
+ assertFalse(map.keySet().contains("zero"));
+ }
+
+ /**
+ * Test method for {@link org.unitConverter.math.ZeroIsNullMap#values()}.
+ */
+ @Test
+ void testValues() {
+ final Map map = this.getTestMap();
+ assertFalse(map.values().contains(0));
+ }
+
+}
--
cgit v1.2.3
From 145f524f19b3d61cd12441c2f1a8de05d7dcfe60 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 16 Oct 2019 12:31:09 -0400
Subject: Created the new BaseUnit, Unit and AbstractUnit implementations.
---
src/org/unitConverter/newUnits/AbstractUnit.java | 76 +++++++++++
src/org/unitConverter/newUnits/BaseUnit.java | 102 +++++++++++++++
src/org/unitConverter/newUnits/FunctionalUnit.java | 98 ++++++++++++++
src/org/unitConverter/newUnits/Unit.java | 145 +++++++++++++++++++++
src/org/unitConverter/newUnits/package-info.java | 23 ++++
5 files changed, 444 insertions(+)
create mode 100644 src/org/unitConverter/newUnits/AbstractUnit.java
create mode 100644 src/org/unitConverter/newUnits/BaseUnit.java
create mode 100644 src/org/unitConverter/newUnits/FunctionalUnit.java
create mode 100644 src/org/unitConverter/newUnits/Unit.java
create mode 100644 src/org/unitConverter/newUnits/package-info.java
diff --git a/src/org/unitConverter/newUnits/AbstractUnit.java b/src/org/unitConverter/newUnits/AbstractUnit.java
new file mode 100644
index 0000000..909ea8b
--- /dev/null
+++ b/src/org/unitConverter/newUnits/AbstractUnit.java
@@ -0,0 +1,76 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.unitConverter.dimension.BaseDimension;
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public abstract class AbstractUnit implements Unit {
+ /**
+ * The combination of units that this unit is based on.
+ */
+ private final ObjectProduct unitBase;
+
+ /**
+ * Cache storing the result of getDimension()
+ */
+ private transient ObjectProduct dimension = null;
+
+ /**
+ * Creates the {@code AbstractUnit}.
+ *
+ * @param unitBase
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if unitBase is null
+ */
+ public AbstractUnit(final ObjectProduct unitBase) {
+ this.unitBase = Objects.requireNonNull(unitBase, "unitBase must not be null.");
+ }
+
+ /**
+ * @return unitBase
+ * @since 2019-10-16
+ */
+ @Override
+ public final ObjectProduct getBase() {
+ return this.unitBase;
+ }
+
+ @Override
+ public ObjectProduct getDimension() {
+ if (this.dimension == null) {
+ final Map mapping = this.unitBase.exponentMap();
+ final Map dimensionMap = new HashMap<>();
+
+ for (final BaseUnit key : mapping.keySet()) {
+ dimensionMap.put(key.getBaseDimension(), mapping.get(key));
+ }
+
+ this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
+ }
+ return this.dimension;
+ }
+}
diff --git a/src/org/unitConverter/newUnits/BaseUnit.java b/src/org/unitConverter/newUnits/BaseUnit.java
new file mode 100644
index 0000000..69e8b8b
--- /dev/null
+++ b/src/org/unitConverter/newUnits/BaseUnit.java
@@ -0,0 +1,102 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+import java.util.Objects;
+
+import org.unitConverter.dimension.BaseDimension;
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * A unit that other units are defined by.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class BaseUnit implements Unit {
+ private final BaseDimension dimension;
+ private final String name;
+ private final String symbol;
+
+ /**
+ * Creates the {@code BaseUnit}.
+ *
+ * @param dimension
+ * dimension of unit
+ * @param name
+ * 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 name, final String symbol) {
+ this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
+ this.name = Objects.requireNonNull(name, "name must not be null.");
+ this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
+ }
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value;
+ }
+
+ @Override
+ public ObjectProduct getBase() {
+ return ObjectProduct.oneOf(this);
+ }
+
+ /**
+ * @return dimension
+ * @since 2019-10-16
+ */
+ public final BaseDimension getBaseDimension() {
+ return this.dimension;
+ }
+
+ @Override
+ public ObjectProduct getDimension() {
+ return ObjectProduct.oneOf(this.getBaseDimension());
+ }
+
+ /**
+ * @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/org/unitConverter/newUnits/FunctionalUnit.java b/src/org/unitConverter/newUnits/FunctionalUnit.java
new file mode 100644
index 0000000..99ff833
--- /dev/null
+++ b/src/org/unitConverter/newUnits/FunctionalUnit.java
@@ -0,0 +1,98 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+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 AbstractUnit {
+ /**
+ * Returns a unit from its base and the functions it uses to convert to and from its base.
+ *
+ * @param base
+ * unit's base
+ * @param converterFrom
+ * function that accepts a value expressed in the unit's base and returns that value expressed in this
+ * unit.
+ * @param converterTo
+ * function that accepts a value expressed in the unit and returns that value expressed in the unit's
+ * base.
+ * @return a unit that uses the provided functions to convert.
+ * @since 2019-05-22
+ * @throws NullPointerException
+ * if any argument is null
+ */
+ public static FunctionalUnit valueOf(final ObjectProduct base, final DoubleUnaryOperator converterFrom,
+ final DoubleUnaryOperator converterTo) {
+ return new FunctionalUnit(base, converterFrom, converterTo);
+ }
+
+ /**
+ * 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
+ */
+ private FunctionalUnit(final ObjectProduct base, final DoubleUnaryOperator converterFrom,
+ final DoubleUnaryOperator converterTo) {
+ super(base);
+ this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null.");
+ this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null.");
+ }
+
+ @Override
+ public double convertFromBase(final double value) {
+ return this.converterFrom.applyAsDouble(value);
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return this.converterTo.applyAsDouble(value);
+ }
+
+}
diff --git a/src/org/unitConverter/newUnits/Unit.java b/src/org/unitConverter/newUnits/Unit.java
new file mode 100644
index 0000000..339afde
--- /dev/null
+++ b/src/org/unitConverter/newUnits/Unit.java
@@ -0,0 +1,145 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+import java.util.Objects;
+import java.util.function.DoubleUnaryOperator;
+
+import org.unitConverter.dimension.BaseDimension;
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public interface Unit {
+ /**
+ * Returns a unit from its base and the functions it uses to convert to and from its base.
+ *
+ *
+ * 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);}
+ *
+ *
+ * @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 Unit fromConversionFunctions(final ObjectProduct base,
+ final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo) {
+ return FunctionalUnit.valueOf(base, converterFrom, converterTo);
+ }
+
+ /**
+ * Checks if a value expressed in this unit can be converted to a value expressed in {@code other}
+ *
+ * @param other
+ * unit to test with
+ * @return true if the units are compatible
+ * @since 2019-01-13
+ * @since v0.1.0
+ * @throws NullPointerException
+ * if other is null
+ */
+ default boolean canConvertTo(final Unit 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.
+ *
+ * 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.
+ *
+ *
+ * If this unit is a base unit, this method should return {@code value}.
+ *
+ *
+ * @param value
+ * value expressed in base unit
+ * @return value expressed in this unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ double convertFromBase(double value);
+
+ /**
+ * Converts a value expressed in this unit to a value expressed in {@code other}.
+ *
+ * @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
+ */
+ default 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 from a value expressed in this unit to a value expressed in this unit's base unit.
+ *
+ * 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.
+ *
+ *
+ * If this unit is a base unit, this method should return {@code value}.
+ *
+ *
+ * @param value
+ * value expressed in this unit
+ * @return value expressed in base unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ double convertToBase(double value);
+
+ /**
+ * @return combination of units that this unit is based on
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ ObjectProduct getBase();
+
+ /**
+ * @return dimension measured by this unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ ObjectProduct getDimension();
+}
\ No newline at end of file
diff --git a/src/org/unitConverter/newUnits/package-info.java b/src/org/unitConverter/newUnits/package-info.java
new file mode 100644
index 0000000..9cd0d1a
--- /dev/null
+++ b/src/org/unitConverter/newUnits/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * 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 .
+ */
+/**
+ * The new definition for units.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+package org.unitConverter.newUnits;
\ No newline at end of file
--
cgit v1.2.3
From 45286c30f96f152f821098d878ede64ecbabe48a Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 16 Oct 2019 13:53:29 -0400
Subject: Added the LinearUnit to the new units definition.
---
src/org/unitConverter/math/ObjectProduct.java | 28 ++-
src/org/unitConverter/newUnits/AbstractUnit.java | 7 +-
src/org/unitConverter/newUnits/BaseUnit.java | 11 +
src/org/unitConverter/newUnits/LinearUnit.java | 261 +++++++++++++++++++++++
src/org/unitConverter/newUnits/Unit.java | 2 +
5 files changed, 301 insertions(+), 8 deletions(-)
create mode 100644 src/org/unitConverter/newUnits/LinearUnit.java
diff --git a/src/org/unitConverter/math/ObjectProduct.java b/src/org/unitConverter/math/ObjectProduct.java
index ec4d2d6..21ab207 100644
--- a/src/org/unitConverter/math/ObjectProduct.java
+++ b/src/org/unitConverter/math/ObjectProduct.java
@@ -24,6 +24,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Function;
/**
* An immutable product of multiple objects of a type, such as base units. The objects can be multiplied and
@@ -177,10 +178,10 @@ public final class ObjectProduct {
}
/**
- * @return true if this product is a "base", i.e. it has one exponent of one and no other nonzero exponents
+ * @return true if this product is a single object, i.e. it has one exponent of one and no other nonzero exponents
* @since 2019-10-16
*/
- public boolean isBase() {
+ public boolean isSingleObject() {
int oneCount = 0;
boolean twoOrMore = false; // has exponents of 2 or more
for (final T b : this.getBaseSet()) {
@@ -238,16 +239,29 @@ public final class ObjectProduct {
@Override
public String toString() {
+ return this.toString(Object::toString);
+ }
+
+ /**
+ * Converts this product to a string. The objects that make up this product are represented by
+ * {@code objectToString}
+ *
+ * @param objectToString
+ * function to convert objects to strings
+ * @return string representation of product
+ * @since 2019-10-16
+ */
+ public String toString(final Function objectToString) {
final List positiveStringComponents = new ArrayList<>();
final List negativeStringComponents = new ArrayList<>();
- // for each base dimension that makes up this dimension, add it and its exponent
- for (final T dimension : this.getBaseSet()) {
- final int exponent = this.exponents.get(dimension);
+ // for each base object that makes up this object, add it and its exponent
+ for (final T object : this.getBaseSet()) {
+ final int exponent = this.exponents.get(object);
if (exponent > 0) {
- positiveStringComponents.add(String.format("%s^%d", dimension, exponent));
+ positiveStringComponents.add(String.format("%s^%d", objectToString.apply(object), exponent));
} else if (exponent < 0) {
- negativeStringComponents.add(String.format("%s^%d", dimension, -exponent));
+ negativeStringComponents.add(String.format("%s^%d", objectToString.apply(object), -exponent));
}
}
diff --git a/src/org/unitConverter/newUnits/AbstractUnit.java b/src/org/unitConverter/newUnits/AbstractUnit.java
index 909ea8b..bc4608e 100644
--- a/src/org/unitConverter/newUnits/AbstractUnit.java
+++ b/src/org/unitConverter/newUnits/AbstractUnit.java
@@ -60,7 +60,7 @@ public abstract class AbstractUnit implements Unit {
}
@Override
- public ObjectProduct getDimension() {
+ public final ObjectProduct getDimension() {
if (this.dimension == null) {
final Map mapping = this.unitBase.exponentMap();
final Map dimensionMap = new HashMap<>();
@@ -73,4 +73,9 @@ public abstract class AbstractUnit implements Unit {
}
return this.dimension;
}
+
+ @Override
+ public String toString() {
+ return "Unit derived from base " + this.getBase().toString();
+ }
}
diff --git a/src/org/unitConverter/newUnits/BaseUnit.java b/src/org/unitConverter/newUnits/BaseUnit.java
index 69e8b8b..b7577ff 100644
--- a/src/org/unitConverter/newUnits/BaseUnit.java
+++ b/src/org/unitConverter/newUnits/BaseUnit.java
@@ -51,6 +51,17 @@ public final class BaseUnit implements Unit {
this.symbol = Objects.requireNonNull(symbol, "symbol 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
public double convertFromBase(final double value) {
return value;
diff --git a/src/org/unitConverter/newUnits/LinearUnit.java b/src/org/unitConverter/newUnits/LinearUnit.java
new file mode 100644
index 0000000..5a589b7
--- /dev/null
+++ b/src/org/unitConverter/newUnits/LinearUnit.java
@@ -0,0 +1,261 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+import java.util.Objects;
+
+import org.unitConverter.math.DecimalComparison;
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * 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 AbstractUnit {
+ /**
+ * 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
+ */
+ public static LinearUnit valueOf(final ObjectProduct unitBase, final double conversionFactor) {
+ return new LinearUnit(unitBase, conversionFactor);
+ }
+
+ /**
+ * The value of this unit as represented in its base form. Mathematically,
+ *
+ *
+ * this = conversionFactor * getBase()
+ *
+ *
+ * @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 unitBase, final double conversionFactor) {
+ super(unitBase);
+ this.conversionFactor = conversionFactor;
+ }
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value / this.getConversionFactor();
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value * 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 base = this.getBase().dividedBy(divisor.getBase());
+ return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor());
+ }
+
+ @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;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.getBase(), 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.
+ *
+ * Two units can be subtracted if they have the same base. If {@code subtrahend} does not meet this condition, an
+ * {@code IllegalArgumentException} will be thrown.
+ *
+ *
+ * @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 subtrahendend) {
+ Objects.requireNonNull(subtrahendend, "addend must not be null.");
+
+ // reject subtrahends that cannot be added to this unit
+ if (!this.getBase().equals(subtrahendend.getBase()))
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahendend));
+
+ // add the units
+ return valueOf(this.getBase(), this.getConversionFactor() - subtrahendend.getConversionFactor());
+ }
+
+ /**
+ * Returns the sum of this unit and another.
+ *
+ * Two units can be added if they have the same base. If {@code addend} does not meet this condition, an
+ * {@code IllegalArgumentException} will be thrown.
+ *
+ *
+ * @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 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));
+ }
+
+ // returns a definition of the unit
+ @Override
+ public String toString() {
+ return Double.toString(this.conversionFactor) + " * " + this.getBase().toString(BaseUnit::getSymbol);
+ }
+
+}
diff --git a/src/org/unitConverter/newUnits/Unit.java b/src/org/unitConverter/newUnits/Unit.java
index 339afde..b50115d 100644
--- a/src/org/unitConverter/newUnits/Unit.java
+++ b/src/org/unitConverter/newUnits/Unit.java
@@ -23,6 +23,8 @@ import org.unitConverter.dimension.BaseDimension;
import org.unitConverter.math.ObjectProduct;
/**
+ * A unit described in terms of base units.
+ *
* @author Adrien Hopkins
* @since 2019-10-16
*/
--
cgit v1.2.3
From 9eba2e72ecba1f33c73d358eb509f0a0816aa810 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 16 Oct 2019 14:41:44 -0400
Subject: Added unit prefixes.
---
src/org/unitConverter/math/DecimalComparison.java | 67 +++++++++++
src/org/unitConverter/newUnits/LinearUnit.java | 29 ++++-
src/org/unitConverter/newUnits/UnitPrefix.java | 134 ++++++++++++++++++++++
3 files changed, 229 insertions(+), 1 deletion(-)
create mode 100644 src/org/unitConverter/newUnits/UnitPrefix.java
diff --git a/src/org/unitConverter/math/DecimalComparison.java b/src/org/unitConverter/math/DecimalComparison.java
index 7cdbe5b..859e8da 100644
--- a/src/org/unitConverter/math/DecimalComparison.java
+++ b/src/org/unitConverter/math/DecimalComparison.java
@@ -16,6 +16,8 @@
*/
package org.unitConverter.math;
+import java.math.BigDecimal;
+
/**
* A class that contains methods to compare float and double values.
*
@@ -44,6 +46,18 @@ public final class DecimalComparison {
/**
* Tests for equality of double values using {@link #DOUBLE_EPSILON}.
+ *
+ * WARNING: this method is not technically transitive. If a and b are off by slightly less than
+ * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than
+ * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c)
+ * will return false. However, this situation is very unlikely to ever happen in a real programming situation.
+ *
+ * If this does become a concern, some ways to solve this problem:
+ *
+ * - Raise the value of epsilon using {@link #equals(double, double, double)} (this does not make a violation of
+ * transitivity impossible, it just significantly reduces the chances of it happening)
+ *
- Use {@link BigDecimal} instead of {@code double} (this will make a violation of transitivity 100% impossible)
+ *
*
* @param a
* first value to test
@@ -52,6 +66,7 @@ public final class DecimalComparison {
* @return whether they are equal
* @since 2019-03-18
* @since v0.2.0
+ * @see #hashCode(double)
*/
public static final boolean equals(final double a, final double b) {
return DecimalComparison.equals(a, b, DOUBLE_EPSILON);
@@ -60,6 +75,19 @@ public final class DecimalComparison {
/**
* Tests for double equality using a custom epsilon value.
*
+ *
+ * WARNING: this method is not technically transitive. If a and b are off by slightly less than
+ * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than
+ * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c)
+ * will return false. However, this situation is very unlikely to ever happen in a real programming situation.
+ *
+ * If this does become a concern, some ways to solve this problem:
+ *
+ * - Raise the value of epsilon (this does not make a violation of transitivity impossible, it just significantly
+ * reduces the chances of it happening)
+ *
- Use {@link BigDecimal} instead of {@code double} (this will make a violation of transitivity 100% impossible)
+ *
+ *
* @param a
* first value to test
* @param b
@@ -77,6 +105,19 @@ public final class DecimalComparison {
/**
* Tests for equality of float values using {@link #FLOAT_EPSILON}.
*
+ *
+ * WARNING: this method is not technically transitive. If a and b are off by slightly less than
+ * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than
+ * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c)
+ * will return false. However, this situation is very unlikely to ever happen in a real programming situation.
+ *
+ * If this does become a concern, some ways to solve this problem:
+ *
+ * - Raise the value of epsilon using {@link #equals(float, float, float)} (this does not make a violation of
+ * transitivity impossible, it just significantly reduces the chances of it happening)
+ *
- Use {@link BigDecimal} instead of {@code float} (this will make a violation of transitivity 100% impossible)
+ *
+ *
* @param a
* first value to test
* @param b
@@ -92,6 +133,19 @@ public final class DecimalComparison {
/**
* Tests for float equality using a custom epsilon value.
*
+ *
+ * WARNING: this method is not technically transitive. If a and b are off by slightly less than
+ * {@code epsilon * max(abs(a), abs(b))}, and b and c are off by slightly less than
+ * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) will both return true, but equals(a, c)
+ * will return false. However, this situation is very unlikely to ever happen in a real programming situation.
+ *
+ * If this does become a concern, some ways to solve this problem:
+ *
+ * - Raise the value of epsilon (this does not make a violation of transitivity impossible, it just significantly
+ * reduces the chances of it happening)
+ *
- Use {@link BigDecimal} instead of {@code float} (this will make a violation of transitivity 100% impossible)
+ *
+ *
* @param a
* first value to test
* @param b
@@ -106,6 +160,19 @@ public final class DecimalComparison {
return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
}
+ /**
+ * Takes the hash code of doubles. Values that are equal according to {@link #equals(double, double)} will have the
+ * same hash code.
+ *
+ * @param d
+ * double to hash
+ * @return hash code of double
+ * @since 2019-10-16
+ */
+ public static final int hash(final double d) {
+ return Float.hashCode((float) d);
+ }
+
// You may NOT get any DecimalComparison instances
private DecimalComparison() {
throw new AssertionError();
diff --git a/src/org/unitConverter/newUnits/LinearUnit.java b/src/org/unitConverter/newUnits/LinearUnit.java
index 5a589b7..7600dad 100644
--- a/src/org/unitConverter/newUnits/LinearUnit.java
+++ b/src/org/unitConverter/newUnits/LinearUnit.java
@@ -28,6 +28,21 @@ import org.unitConverter.math.ObjectProduct;
* @since 2019-10-16
*/
public final class LinearUnit extends AbstractUnit {
+ /**
+ * 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
+ */
+ public static LinearUnit fromValue(final Unit unit, final double value) {
+ return new LinearUnit(unit.getBase(), unit.convertToBase(value));
+ }
+
/**
* 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}.
@@ -129,7 +144,7 @@ public final class LinearUnit extends AbstractUnit {
@Override
public int hashCode() {
- return Objects.hash(this.getBase(), this.getConversionFactor());
+ return 31 * this.getBase().hashCode() + DecimalComparison.hash(this.getConversionFactor());
}
/**
@@ -258,4 +273,16 @@ public final class LinearUnit extends AbstractUnit {
return Double.toString(this.conversionFactor) + " * " + this.getBase().toString(BaseUnit::getSymbol);
}
+ /**
+ * Returns the result of applying {@code prefix} to this unit.
+ *
+ * @param prefix
+ * prefix to apply
+ * @return unit with prefix
+ * @since 2019-03-18
+ * @since v0.2.0
+ */
+ public LinearUnit withPrefix(final UnitPrefix prefix) {
+ return this.times(prefix.getMultiplier());
+ }
}
diff --git a/src/org/unitConverter/newUnits/UnitPrefix.java b/src/org/unitConverter/newUnits/UnitPrefix.java
new file mode 100644
index 0000000..905ca19
--- /dev/null
+++ b/src/org/unitConverter/newUnits/UnitPrefix.java
@@ -0,0 +1,134 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+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 {
+ /**
+ * 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) {
+ this.multiplier = multiplier;
+ }
+
+ /**
+ * 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 new UnitPrefix(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 new UnitPrefix(this.getMultiplier() / other.getMultiplier());
+ }
+
+ @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());
+ }
+
+ public double getMultiplier() {
+ return this.multiplier;
+ }
+
+ @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 new UnitPrefix(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 new UnitPrefix(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 new UnitPrefix(Math.pow(this.getMultiplier(), exponent));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Unit prefix equal to %s", this.multiplier);
+ }
+}
--
cgit v1.2.3
From abe715a30844537693ae186308adcab62c66f121 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 16 Oct 2019 15:35:30 -0400
Subject: Made Unit an abstract class.
The abstract unit conversion methods are now protected.
---
src/org/unitConverter/newUnits/AbstractUnit.java | 81 --------------------
src/org/unitConverter/newUnits/BaseUnit.java | 14 +---
src/org/unitConverter/newUnits/FunctionalUnit.java | 2 +-
src/org/unitConverter/newUnits/LinearUnit.java | 8 +-
src/org/unitConverter/newUnits/Unit.java | 88 ++++++++++++++++++----
5 files changed, 79 insertions(+), 114 deletions(-)
delete mode 100644 src/org/unitConverter/newUnits/AbstractUnit.java
diff --git a/src/org/unitConverter/newUnits/AbstractUnit.java b/src/org/unitConverter/newUnits/AbstractUnit.java
deleted file mode 100644
index bc4608e..0000000
--- a/src/org/unitConverter/newUnits/AbstractUnit.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-
-import org.unitConverter.dimension.BaseDimension;
-import org.unitConverter.math.ObjectProduct;
-
-/**
- * @author Adrien Hopkins
- * @since 2019-10-16
- */
-public abstract class AbstractUnit implements Unit {
- /**
- * The combination of units that this unit is based on.
- */
- private final ObjectProduct unitBase;
-
- /**
- * Cache storing the result of getDimension()
- */
- private transient ObjectProduct dimension = null;
-
- /**
- * Creates the {@code AbstractUnit}.
- *
- * @param unitBase
- * @since 2019-10-16
- * @throws NullPointerException
- * if unitBase is null
- */
- public AbstractUnit(final ObjectProduct unitBase) {
- this.unitBase = Objects.requireNonNull(unitBase, "unitBase must not be null.");
- }
-
- /**
- * @return unitBase
- * @since 2019-10-16
- */
- @Override
- public final ObjectProduct getBase() {
- return this.unitBase;
- }
-
- @Override
- public final ObjectProduct getDimension() {
- if (this.dimension == null) {
- final Map mapping = this.unitBase.exponentMap();
- final Map dimensionMap = new HashMap<>();
-
- for (final BaseUnit key : mapping.keySet()) {
- dimensionMap.put(key.getBaseDimension(), mapping.get(key));
- }
-
- this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
- }
- return this.dimension;
- }
-
- @Override
- public String toString() {
- return "Unit derived from base " + this.getBase().toString();
- }
-}
diff --git a/src/org/unitConverter/newUnits/BaseUnit.java b/src/org/unitConverter/newUnits/BaseUnit.java
index b7577ff..2c4d748 100644
--- a/src/org/unitConverter/newUnits/BaseUnit.java
+++ b/src/org/unitConverter/newUnits/BaseUnit.java
@@ -19,7 +19,6 @@ package org.unitConverter.newUnits;
import java.util.Objects;
import org.unitConverter.dimension.BaseDimension;
-import org.unitConverter.math.ObjectProduct;
/**
* A unit that other units are defined by.
@@ -27,7 +26,7 @@ import org.unitConverter.math.ObjectProduct;
* @author Adrien Hopkins
* @since 2019-10-16
*/
-public final class BaseUnit implements Unit {
+public final class BaseUnit extends Unit {
private final BaseDimension dimension;
private final String name;
private final String symbol;
@@ -46,6 +45,7 @@ public final class BaseUnit implements Unit {
* @since 2019-10-16
*/
private BaseUnit(final BaseDimension dimension, final String name, final String symbol) {
+ super();
this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
this.name = Objects.requireNonNull(name, "name must not be null.");
this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
@@ -72,11 +72,6 @@ public final class BaseUnit implements Unit {
return value;
}
- @Override
- public ObjectProduct getBase() {
- return ObjectProduct.oneOf(this);
- }
-
/**
* @return dimension
* @since 2019-10-16
@@ -85,11 +80,6 @@ public final class BaseUnit implements Unit {
return this.dimension;
}
- @Override
- public ObjectProduct getDimension() {
- return ObjectProduct.oneOf(this.getBaseDimension());
- }
-
/**
* @return name
* @since 2019-10-16
diff --git a/src/org/unitConverter/newUnits/FunctionalUnit.java b/src/org/unitConverter/newUnits/FunctionalUnit.java
index 99ff833..6bff3e8 100644
--- a/src/org/unitConverter/newUnits/FunctionalUnit.java
+++ b/src/org/unitConverter/newUnits/FunctionalUnit.java
@@ -27,7 +27,7 @@ import org.unitConverter.math.ObjectProduct;
* @author Adrien Hopkins
* @since 2019-05-22
*/
-final class FunctionalUnit extends AbstractUnit {
+final class FunctionalUnit extends Unit {
/**
* Returns a unit from its base and the functions it uses to convert to and from its base.
*
diff --git a/src/org/unitConverter/newUnits/LinearUnit.java b/src/org/unitConverter/newUnits/LinearUnit.java
index 7600dad..c8c610e 100644
--- a/src/org/unitConverter/newUnits/LinearUnit.java
+++ b/src/org/unitConverter/newUnits/LinearUnit.java
@@ -27,7 +27,7 @@ import org.unitConverter.math.ObjectProduct;
* @author Adrien Hopkins
* @since 2019-10-16
*/
-public final class LinearUnit extends AbstractUnit {
+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'
@@ -39,7 +39,7 @@ public final class LinearUnit extends AbstractUnit {
* @return value expressed as a {@code LinearUnit}
* @since 2019-10-16
*/
- public static LinearUnit fromValue(final Unit unit, final double value) {
+ public static LinearUnit fromUnitValue(final Unit unit, final double value) {
return new LinearUnit(unit.getBase(), unit.convertToBase(value));
}
@@ -84,12 +84,12 @@ public final class LinearUnit extends AbstractUnit {
}
@Override
- public double convertFromBase(final double value) {
+ protected double convertFromBase(final double value) {
return value / this.getConversionFactor();
}
@Override
- public double convertToBase(final double value) {
+ protected double convertToBase(final double value) {
return value * this.getConversionFactor();
}
diff --git a/src/org/unitConverter/newUnits/Unit.java b/src/org/unitConverter/newUnits/Unit.java
index b50115d..feeb25e 100644
--- a/src/org/unitConverter/newUnits/Unit.java
+++ b/src/org/unitConverter/newUnits/Unit.java
@@ -19,16 +19,15 @@ package org.unitConverter.newUnits;
import java.util.Objects;
import java.util.function.DoubleUnaryOperator;
-import org.unitConverter.dimension.BaseDimension;
import org.unitConverter.math.ObjectProduct;
/**
- * A unit described in terms of base units.
+ * A unit that is composed of base units.
*
* @author Adrien Hopkins
* @since 2019-10-16
*/
-public interface Unit {
+public abstract class Unit {
/**
* Returns a unit from its base and the functions it uses to convert to and from its base.
*
@@ -56,6 +55,44 @@ public interface Unit {
return FunctionalUnit.valueOf(base, converterFrom, converterTo);
}
+ /**
+ * The combination of units that this unit is based on.
+ *
+ * @since 2019-10-16
+ */
+ private final ObjectProduct unitBase;
+
+ // /**
+ // * Cache storing the result of getDimension()
+ // *
+ // * @since 2019-10-16
+ // */
+ // private transient ObjectProduct dimension = null;
+
+ /**
+ * A constructor that constructs {@code BaseUnit} instances.
+ *
+ * @since 2019-10-16
+ */
+ Unit() {
+ if (this instanceof BaseUnit) {
+ this.unitBase = ObjectProduct.oneOf((BaseUnit) this);
+ } else
+ throw new AssertionError();
+ }
+
+ /**
+ * Creates the {@code AbstractUnit}.
+ *
+ * @param unitBase
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if unitBase is null
+ */
+ protected Unit(final ObjectProduct unitBase) {
+ this.unitBase = Objects.requireNonNull(unitBase, "unitBase must not be null.");
+ }
+
/**
* Checks if a value expressed in this unit can be converted to a value expressed in {@code other}
*
@@ -67,7 +104,7 @@ public interface Unit {
* @throws NullPointerException
* if other is null
*/
- default boolean canConvertTo(final Unit other) {
+ public final boolean canConvertTo(final Unit other) {
Objects.requireNonNull(other, "other must not be null.");
return Objects.equals(this.getBase(), other.getBase());
}
@@ -88,7 +125,7 @@ public interface Unit {
* @since 2018-12-22
* @since v0.1.0
*/
- double convertFromBase(double value);
+ protected abstract double convertFromBase(double value);
/**
* Converts a value expressed in this unit to a value expressed in {@code other}.
@@ -101,11 +138,11 @@ public interface Unit {
* @since 2019-05-22
* @throws IllegalArgumentException
* if {@code other} is incompatible for conversion with this unit (as tested by
- * {@link Unit#canConvertTo}).
+ * {@link IUnit#canConvertTo}).
* @throws NullPointerException
* if other is null
*/
- default double convertTo(final Unit other, final double value) {
+ 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));
@@ -129,19 +166,38 @@ public interface Unit {
* @since 2018-12-22
* @since v0.1.0
*/
- double convertToBase(double value);
+ protected abstract double convertToBase(double value);
/**
* @return combination of units that this unit is based on
* @since 2018-12-22
* @since v0.1.0
*/
- ObjectProduct getBase();
+ public final ObjectProduct getBase() {
+ return this.unitBase;
+ }
- /**
- * @return dimension measured by this unit
- * @since 2018-12-22
- * @since v0.1.0
- */
- ObjectProduct getDimension();
-}
\ No newline at end of file
+ // /**
+ // * @return dimension measured by this unit
+ // * @since 2018-12-22
+ // * @since v0.1.0
+ // */
+ // private final ObjectProduct getDimension() {
+ // if (this.dimension == null) {
+ // final Map mapping = this.unitBase.exponentMap();
+ // final Map dimensionMap = new HashMap<>();
+ //
+ // for (final BaseUnit key : mapping.keySet()) {
+ // dimensionMap.put(key.getBaseDimension(), mapping.get(key));
+ // }
+ //
+ // this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
+ // }
+ // return this.dimension;
+ // }
+
+ @Override
+ public String toString() {
+ return "Unit derived from base " + this.getBase().toString();
+ }
+}
--
cgit v1.2.3
From df06497dc4d7359de006c5885074f3356dbb81de Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 16 Oct 2019 16:50:04 -0400
Subject: Added new constructors for BaseUnit and BaseDimension.
---
src/org/unitConverter/newUnits/BaseUnit.java | 18 ++++++++--
src/org/unitConverter/newUnits/Unit.java | 50 +++++++++++++-------------
src/org/unitConverter/newUnits/UnitPrefix.java | 22 +++++++++---
3 files changed, 59 insertions(+), 31 deletions(-)
diff --git a/src/org/unitConverter/newUnits/BaseUnit.java b/src/org/unitConverter/newUnits/BaseUnit.java
index 2c4d748..6a57faa 100644
--- a/src/org/unitConverter/newUnits/BaseUnit.java
+++ b/src/org/unitConverter/newUnits/BaseUnit.java
@@ -18,8 +18,6 @@ package org.unitConverter.newUnits;
import java.util.Objects;
-import org.unitConverter.dimension.BaseDimension;
-
/**
* A unit that other units are defined by.
*
@@ -27,6 +25,22 @@ import org.unitConverter.dimension.BaseDimension;
* @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);
+ }
+
private final BaseDimension dimension;
private final String name;
private final String symbol;
diff --git a/src/org/unitConverter/newUnits/Unit.java b/src/org/unitConverter/newUnits/Unit.java
index feeb25e..339ab95 100644
--- a/src/org/unitConverter/newUnits/Unit.java
+++ b/src/org/unitConverter/newUnits/Unit.java
@@ -16,6 +16,8 @@
*/
package org.unitConverter.newUnits;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
import java.util.function.DoubleUnaryOperator;
@@ -62,12 +64,12 @@ public abstract class Unit {
*/
private final ObjectProduct unitBase;
- // /**
- // * Cache storing the result of getDimension()
- // *
- // * @since 2019-10-16
- // */
- // private transient ObjectProduct dimension = null;
+ /**
+ * Cache storing the result of getDimension()
+ *
+ * @since 2019-10-16
+ */
+ private transient ObjectProduct dimension = null;
/**
* A constructor that constructs {@code BaseUnit} instances.
@@ -177,24 +179,24 @@ public abstract class Unit {
return this.unitBase;
}
- // /**
- // * @return dimension measured by this unit
- // * @since 2018-12-22
- // * @since v0.1.0
- // */
- // private final ObjectProduct getDimension() {
- // if (this.dimension == null) {
- // final Map mapping = this.unitBase.exponentMap();
- // final Map 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 dimension measured by this unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public final ObjectProduct getDimension() {
+ if (this.dimension == null) {
+ final Map mapping = this.unitBase.exponentMap();
+ final Map dimensionMap = new HashMap<>();
+
+ for (final BaseUnit key : mapping.keySet()) {
+ dimensionMap.put(key.getBaseDimension(), mapping.get(key));
+ }
+
+ this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
+ }
+ return this.dimension;
+ }
@Override
public String toString() {
diff --git a/src/org/unitConverter/newUnits/UnitPrefix.java b/src/org/unitConverter/newUnits/UnitPrefix.java
index 905ca19..5608098 100644
--- a/src/org/unitConverter/newUnits/UnitPrefix.java
+++ b/src/org/unitConverter/newUnits/UnitPrefix.java
@@ -25,6 +25,18 @@ import org.unitConverter.math.DecimalComparison;
* @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);
+ }
+
/**
* The number that this prefix multiplies units by
*
@@ -52,7 +64,7 @@ public final class UnitPrefix {
* @since 2019-10-16
*/
public UnitPrefix dividedBy(final double divisor) {
- return new UnitPrefix(this.getMultiplier() / divisor);
+ return valueOf(this.getMultiplier() / divisor);
}
/**
@@ -65,7 +77,7 @@ public final class UnitPrefix {
* @since v0.2.0
*/
public UnitPrefix dividedBy(final UnitPrefix other) {
- return new UnitPrefix(this.getMultiplier() / other.getMultiplier());
+ return valueOf(this.getMultiplier() / other.getMultiplier());
}
@Override
@@ -98,7 +110,7 @@ public final class UnitPrefix {
* @since 2019-10-16
*/
public UnitPrefix times(final double multiplicand) {
- return new UnitPrefix(this.getMultiplier() * multiplicand);
+ return valueOf(this.getMultiplier() * multiplicand);
}
/**
@@ -111,7 +123,7 @@ public final class UnitPrefix {
* @since v0.2.0
*/
public UnitPrefix times(final UnitPrefix other) {
- return new UnitPrefix(this.getMultiplier() * other.getMultiplier());
+ return valueOf(this.getMultiplier() * other.getMultiplier());
}
/**
@@ -124,7 +136,7 @@ public final class UnitPrefix {
* @since v0.2.0
*/
public UnitPrefix toExponent(final double exponent) {
- return new UnitPrefix(Math.pow(this.getMultiplier(), exponent));
+ return valueOf(Math.pow(this.getMultiplier(), exponent));
}
@Override
--
cgit v1.2.3
From 2ff6c4ea25beeab58239ddf576fb89254ba98630 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 16 Oct 2019 17:17:41 -0400
Subject: Added an 'SI' class with all units, dimensions and prefixes in SI.
---
src/org/unitConverter/math/StandardDimensions.java | 86 --------
src/org/unitConverter/newUnits/BaseDimension.java | 81 ++++++++
src/org/unitConverter/newUnits/SI.java | 228 +++++++++++++++++++++
3 files changed, 309 insertions(+), 86 deletions(-)
delete mode 100644 src/org/unitConverter/math/StandardDimensions.java
create mode 100644 src/org/unitConverter/newUnits/BaseDimension.java
create mode 100644 src/org/unitConverter/newUnits/SI.java
diff --git a/src/org/unitConverter/math/StandardDimensions.java b/src/org/unitConverter/math/StandardDimensions.java
deleted file mode 100644
index db5efc3..0000000
--- a/src/org/unitConverter/math/StandardDimensions.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.math;
-
-import org.unitConverter.dimension.BaseDimension;
-import org.unitConverter.dimension.OtherBaseDimension;
-import org.unitConverter.dimension.SIBaseDimension;
-
-/**
- * All of the dimensions that are used by the SI. (Test data for the ObjectProductTest)
- *
- * @author Adrien Hopkins
- * @since 2018-12-11
- * @since v0.1.0
- */
-final class StandardDimensions {
- // base dimensions
- public static final ObjectProduct EMPTY = ObjectProduct.empty();
- public static final ObjectProduct LENGTH = ObjectProduct.oneOf(SIBaseDimension.LENGTH);
- public static final ObjectProduct MASS = ObjectProduct.oneOf(SIBaseDimension.MASS);
- public static final ObjectProduct TIME = ObjectProduct.oneOf(SIBaseDimension.TIME);
- public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct
- .oneOf(SIBaseDimension.ELECTRIC_CURRENT);
- public static final ObjectProduct TEMPERATURE = ObjectProduct.oneOf(SIBaseDimension.TEMPERATURE);
- public static final ObjectProduct QUANTITY = ObjectProduct.oneOf(SIBaseDimension.QUANTITY);
- public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct
- .oneOf(SIBaseDimension.LUMINOUS_INTENSITY);
- public static final ObjectProduct INFORMATION = ObjectProduct.oneOf(OtherBaseDimension.INFORMATION);
- public static final ObjectProduct CURRENCY = ObjectProduct.oneOf(OtherBaseDimension.CURRENCY);
- // derived dimensions without named SI units
- public static final ObjectProduct AREA = LENGTH.times(LENGTH);
-
- public static final ObjectProduct VOLUME = AREA.times(LENGTH);
- public static final ObjectProduct VELOCITY = LENGTH.dividedBy(TIME);
- public static final ObjectProduct ACCELERATION = VELOCITY.dividedBy(TIME);
- public static final ObjectProduct WAVENUMBER = EMPTY.dividedBy(LENGTH);
- public static final ObjectProduct MASS_DENSITY = MASS.dividedBy(VOLUME);
- public static final ObjectProduct SURFACE_DENSITY = MASS.dividedBy(AREA);
- public static final ObjectProduct SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
- public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
- public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
- public static final ObjectProduct CONCENTRATION = QUANTITY.dividedBy(VOLUME);
- public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION.times(MASS);
- public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
- public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
- public static final ObjectProduct REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
- public static final ObjectProduct ANGLE = LENGTH.dividedBy(LENGTH);
- public static final ObjectProduct SOLID_ANGLE = AREA.dividedBy(AREA);
- // derived dimensions with named SI units
- public static final ObjectProduct FREQUENCY = EMPTY.dividedBy(TIME);
-
- public static final ObjectProduct FORCE = MASS.times(ACCELERATION);
- public static final ObjectProduct ENERGY = FORCE.times(LENGTH);
- public static final ObjectProduct POWER = ENERGY.dividedBy(TIME);
- public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
- public static final ObjectProduct VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
- public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
- public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
- public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
- public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE.times(TIME);
- public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
- public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
- public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
- public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
- public static final ObjectProduct SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
- public static final ObjectProduct CATALYTIC_ACTIVITY = QUANTITY.dividedBy(TIME);
-
- // You may NOT get StandardDimensions instances!
- private StandardDimensions() {
- throw new AssertionError();
- }
-}
diff --git a/src/org/unitConverter/newUnits/BaseDimension.java b/src/org/unitConverter/newUnits/BaseDimension.java
new file mode 100644
index 0000000..a1cde46
--- /dev/null
+++ b/src/org/unitConverter/newUnits/BaseDimension.java
@@ -0,0 +1,81 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+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);
+ }
+
+ private final String name;
+ 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/org/unitConverter/newUnits/SI.java b/src/org/unitConverter/newUnits/SI.java
new file mode 100644
index 0000000..b7a117a
--- /dev/null
+++ b/src/org/unitConverter/newUnits/SI.java
@@ -0,0 +1,228 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+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.
+ *
+ *
+ * This class does not include prefixed units. To obtain prefixed units, use {@link LinearUnit#withPrefix}:
+ *
+ *
+ * LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
+ *
+ *
+ *
+ * @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", "$");
+
+ // You may NOT get SI.BaseUnits instances!
+ private BaseUnits() {
+ throw new AssertionError();
+ }
+ }
+
+ // dimensions used in the SI, as ObjectProducts
+ public static final class Dimensions {
+ public static final ObjectProduct EMPTY = ObjectProduct.empty();
+ public static final ObjectProduct LENGTH = ObjectProduct.oneOf(BaseDimensions.LENGTH);
+ public static final ObjectProduct MASS = ObjectProduct.oneOf(BaseDimensions.MASS);
+ public static final ObjectProduct TIME = ObjectProduct.oneOf(BaseDimensions.TIME);
+ public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct
+ .oneOf(BaseDimensions.ELECTRIC_CURRENT);
+ public static final ObjectProduct TEMPERATURE = ObjectProduct.oneOf(BaseDimensions.TEMPERATURE);
+ public static final ObjectProduct QUANTITY = ObjectProduct.oneOf(BaseDimensions.QUANTITY);
+ public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct
+ .oneOf(BaseDimensions.LUMINOUS_INTENSITY);
+ public static final ObjectProduct INFORMATION = ObjectProduct.oneOf(BaseDimensions.INFORMATION);
+ public static final ObjectProduct CURRENCY = ObjectProduct.oneOf(BaseDimensions.CURRENCY);
+ // derived dimensions without named SI units
+ public static final ObjectProduct AREA = LENGTH.times(LENGTH);
+
+ public static final ObjectProduct VOLUME = AREA.times(LENGTH);
+ public static final ObjectProduct VELOCITY = LENGTH.dividedBy(TIME);
+ public static final ObjectProduct ACCELERATION = VELOCITY.dividedBy(TIME);
+ public static final ObjectProduct WAVENUMBER = EMPTY.dividedBy(LENGTH);
+ public static final ObjectProduct MASS_DENSITY = MASS.dividedBy(VOLUME);
+ public static final ObjectProduct SURFACE_DENSITY = MASS.dividedBy(AREA);
+ public static final ObjectProduct SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
+ public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
+ public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
+ public static final ObjectProduct CONCENTRATION = QUANTITY.dividedBy(VOLUME);
+ public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION.times(MASS);
+ public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
+ public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
+ public static final ObjectProduct REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
+ public static final ObjectProduct ANGLE = LENGTH.dividedBy(LENGTH);
+ public static final ObjectProduct SOLID_ANGLE = AREA.dividedBy(AREA);
+ // derived dimensions with named SI units
+ public static final ObjectProduct FREQUENCY = EMPTY.dividedBy(TIME);
+
+ public static final ObjectProduct FORCE = MASS.times(ACCELERATION);
+ public static final ObjectProduct ENERGY = FORCE.times(LENGTH);
+ public static final ObjectProduct POWER = ENERGY.dividedBy(TIME);
+ public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
+ public static final ObjectProduct VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
+ public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
+ public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
+ public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE.times(TIME);
+ public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
+ public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
+ public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
+ public static final ObjectProduct SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
+ public static final ObjectProduct 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();
+ public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit();
+ public static final LinearUnit SECOND = BaseUnits.SECOND.asLinearUnit();
+ public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit();
+ public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit();
+ public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit();
+ public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit();
+ public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit();
+ public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit();
+
+ // Non-base units
+ public static final LinearUnit RADIAN = METRE.dividedBy(METRE);
+ public static final LinearUnit STERADIAN = RADIAN.times(RADIAN);
+ public static final LinearUnit HERTZ = ONE.dividedBy(SECOND); // for periodic phenomena
+ public static final LinearUnit NEWTON = KILOGRAM.times(METRE).dividedBy(SECOND.times(SECOND));
+ public static final LinearUnit PASCAL = NEWTON.dividedBy(METRE.times(METRE));
+ public static final LinearUnit JOULE = NEWTON.times(METRE);
+ public static final LinearUnit WATT = JOULE.dividedBy(SECOND);
+ public static final LinearUnit COULOMB = AMPERE.times(SECOND);
+ public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB);
+ public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT);
+ public static final LinearUnit OHM = VOLT.dividedBy(AMPERE);
+ public static final LinearUnit SIEMENS = ONE.dividedBy(OHM);
+ public static final LinearUnit WEBER = VOLT.times(SECOND);
+ public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE));
+ public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE);
+ public static final LinearUnit LUMEN = CANDELA.times(STERADIAN);
+ public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE));
+ public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND); // for activity referred to a nucleotide
+ public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM); // for absorbed dose
+ public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM); // for dose equivalent
+ public static final LinearUnit KATAL = MOLE.dividedBy(SECOND);
+
+ // Non-SI units included for convenience
+ public static final Unit CELSIUS = Unit.fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15,
+ tempC -> tempC + 273.15);
+ public static final LinearUnit MINUTE = SECOND.times(60);
+ public static final LinearUnit HOUR = MINUTE.times(60);
+ public static final LinearUnit DAY = HOUR.times(60);
+ public static final LinearUnit DEGREE = RADIAN.times(360 / (2 * Math.PI));
+ public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60);
+ public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60);
+ public static final LinearUnit ASTRONOMICAL_UNIT = METRE.times(149597870700.0);
+ public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT.times(ARCSECOND);
+ public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0);
+ public static final LinearUnit LITRE = METRE.times(METRE).times(METRE).dividedBy(1000.0);
+ public static final LinearUnit TONNE = KILOGRAM.times(1000.0);
+ public static final LinearUnit DALTON = KILOGRAM.times(1.660539040e-27); // approximate value
+ public static final LinearUnit ELECTRONVOLT = JOULE.times(1.602176634e-19);
+ public static final Unit NEPER = Unit.fromConversionFunctions(ONE.getBase(), pr -> 0.5 * Math.log(pr),
+ Np -> Math.exp(2 * Np));
+ public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(), pr -> Math.log10(pr),
+ dB -> Math.pow(10, dB));
+ public static final Unit DECIBEL = Unit.fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr),
+ dB -> Math.pow(10, dB / 10));
+
+ /// The prefixes of the SI
+ // expanding decimal prefixes
+ public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3);
+ public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6);
+ public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9);
+ public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12);
+ public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15);
+ public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18);
+ public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21);
+ public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24);
+
+ // contracting decimal prefixes
+ public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3);
+ public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6);
+ public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9);
+ public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12);
+ public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15);
+ public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18);
+ public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21);
+ public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24);
+
+ // prefixes that don't match the pattern of thousands
+ public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1);
+ public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2);
+ public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1);
+ public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2);
+ public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024);
+ public static final UnitPrefix MEBI = KIBI.times(1024);
+ public static final UnitPrefix GIBI = MEBI.times(1024);
+ public static final UnitPrefix TEBI = GIBI.times(1024);
+ public static final UnitPrefix PEBI = TEBI.times(1024);
+ public static final UnitPrefix EXBI = PEBI.times(1024);
+
+ // You may NOT get SI instances!
+ private SI() {
+ throw new AssertionError();
+ }
+}
--
cgit v1.2.3
From f309ef0b9ed24629146d1d92a5c869946a6d65a2 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Thu, 17 Oct 2019 14:10:35 -0400
Subject: Added a test for the new Unit classes
---
src/org/unitConverter/newUnits/UnitTest.java | 102 +++++++++++++++++++++++++++
1 file changed, 102 insertions(+)
create mode 100644 src/org/unitConverter/newUnits/UnitTest.java
diff --git a/src/org/unitConverter/newUnits/UnitTest.java b/src/org/unitConverter/newUnits/UnitTest.java
new file mode 100644
index 0000000..33bd264
--- /dev/null
+++ b/src/org/unitConverter/newUnits/UnitTest.java
@@ -0,0 +1,102 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.newUnits;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.junit.jupiter.api.Test;
+import org.unitConverter.math.DecimalComparison;
+
+/**
+ * Testing the various Unit classes. This is NOT part of this program's public API.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+class UnitTest {
+ /** A random number generator */
+ private static final Random rng = ThreadLocalRandom.current();
+
+ @Test
+ public void testAdditionAndSubtraction() {
+ final LinearUnit inch = SI.METRE.times(0.0254);
+ final LinearUnit foot = SI.METRE.times(0.3048);
+
+ assertEquals(inch.plus(foot), SI.METRE.times(0.3302));
+ assertEquals(foot.minus(inch), SI.METRE.times(0.2794));
+ }
+
+ @Test
+ public void testConversion() {
+ final LinearUnit metre = SI.METRE;
+ final Unit inch = metre.times(0.0254);
+
+ assertEquals(1.9, inch.convertTo(metre, 75), 0.01);
+
+ // try random stuff
+ for (int i = 0; i < 1000; i++) {
+ // initiate random values
+ final double conversionFactor = rng.nextDouble() * 1000000;
+ final double testValue = rng.nextDouble() * 1000000;
+ final double expected = testValue * conversionFactor;
+
+ // test
+ final Unit unit = SI.METRE.times(conversionFactor);
+ final double actual = unit.convertToBase(testValue);
+
+ assertEquals(actual, expected, expected * DecimalComparison.DOUBLE_EPSILON);
+ }
+ }
+
+ @Test
+ public void testEquals() {
+ final LinearUnit metre = SI.METRE;
+ final Unit meter = SI.BaseUnits.METRE.asLinearUnit();
+
+ assertEquals(metre, meter);
+ }
+
+ @Test
+ public void testMultiplicationAndDivision() {
+ // test unit-times-unit multiplication
+ final LinearUnit generatedJoule = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
+ final LinearUnit actualJoule = SI.JOULE;
+
+ assertEquals(generatedJoule, actualJoule);
+
+ // test multiplication by conversion factors
+ final LinearUnit kilometre = SI.METRE.times(1000);
+ final LinearUnit hour = SI.SECOND.times(3600);
+ final LinearUnit generatedKPH = kilometre.dividedBy(hour);
+
+ final LinearUnit actualKPH = SI.METRE.dividedBy(SI.SECOND).dividedBy(3.6);
+
+ assertEquals(generatedKPH, actualKPH);
+ }
+
+ @Test
+ public void testPrefixes() {
+ final LinearUnit generatedKilometre = SI.METRE.withPrefix(SI.KILO);
+ final LinearUnit actualKilometre = SI.METRE.times(1000);
+
+ assertEquals(generatedKilometre, actualKilometre);
+ }
+}
--
cgit v1.2.3
From 54ab9c05234b09547e2a01b1eab812420c6a3dda Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Thu, 17 Oct 2019 14:25:17 -0400
Subject: Implemented the new Units system
Fahrenheit has temporarily been removed; it will be back.
---
src/org/unitConverter/UnitsDatabase.java | 1658 --------------------
src/org/unitConverter/UnitsDatabaseTest.java | 312 ----
.../converterGUI/UnitConverterGUI.java | 35 +-
src/org/unitConverter/dimension/BaseDimension.java | 40 -
.../dimension/OtherBaseDimension.java | 55 -
.../unitConverter/dimension/SIBaseDimension.java | 57 -
.../dimension/StandardDimensions.java | 80 -
src/org/unitConverter/dimension/UnitDimension.java | 241 ---
.../unitConverter/dimension/UnitDimensionTest.java | 77 -
src/org/unitConverter/dimension/package-info.java | 24 -
src/org/unitConverter/math/ObjectProductTest.java | 23 +-
src/org/unitConverter/newUnits/BaseDimension.java | 81 -
src/org/unitConverter/newUnits/BaseUnit.java | 117 --
src/org/unitConverter/newUnits/FunctionalUnit.java | 98 --
src/org/unitConverter/newUnits/LinearUnit.java | 288 ----
src/org/unitConverter/newUnits/SI.java | 228 ---
src/org/unitConverter/newUnits/Unit.java | 205 ---
src/org/unitConverter/newUnits/UnitPrefix.java | 146 --
src/org/unitConverter/newUnits/UnitTest.java | 102 --
src/org/unitConverter/newUnits/package-info.java | 23 -
src/org/unitConverter/unit/AbstractUnit.java | 124 --
src/org/unitConverter/unit/BaseDimension.java | 81 +
src/org/unitConverter/unit/BaseUnit.java | 163 +-
src/org/unitConverter/unit/DefaultUnitPrefix.java | 68 -
src/org/unitConverter/unit/FunctionalUnit.java | 8 +-
src/org/unitConverter/unit/LinearUnit.java | 166 +-
src/org/unitConverter/unit/NonlinearUnits.java | 37 -
src/org/unitConverter/unit/SI.java | 302 +++-
src/org/unitConverter/unit/SIPrefix.java | 54 -
src/org/unitConverter/unit/Unit.java | 105 +-
src/org/unitConverter/unit/UnitDatabase.java | 1653 +++++++++++++++++++
src/org/unitConverter/unit/UnitDatabaseTest.java | 307 ++++
src/org/unitConverter/unit/UnitPrefix.java | 104 +-
src/org/unitConverter/unit/UnitSystem.java | 53 -
src/org/unitConverter/unit/UnitTest.java | 21 +-
src/org/unitConverter/unit/package-info.java | 5 +-
unitsfile.txt | 4 +-
37 files changed, 2610 insertions(+), 4535 deletions(-)
delete mode 100644 src/org/unitConverter/UnitsDatabase.java
delete mode 100644 src/org/unitConverter/UnitsDatabaseTest.java
delete mode 100644 src/org/unitConverter/dimension/BaseDimension.java
delete mode 100644 src/org/unitConverter/dimension/OtherBaseDimension.java
delete mode 100644 src/org/unitConverter/dimension/SIBaseDimension.java
delete mode 100644 src/org/unitConverter/dimension/StandardDimensions.java
delete mode 100644 src/org/unitConverter/dimension/UnitDimension.java
delete mode 100644 src/org/unitConverter/dimension/UnitDimensionTest.java
delete mode 100644 src/org/unitConverter/dimension/package-info.java
delete mode 100644 src/org/unitConverter/newUnits/BaseDimension.java
delete mode 100644 src/org/unitConverter/newUnits/BaseUnit.java
delete mode 100644 src/org/unitConverter/newUnits/FunctionalUnit.java
delete mode 100644 src/org/unitConverter/newUnits/LinearUnit.java
delete mode 100644 src/org/unitConverter/newUnits/SI.java
delete mode 100644 src/org/unitConverter/newUnits/Unit.java
delete mode 100644 src/org/unitConverter/newUnits/UnitPrefix.java
delete mode 100644 src/org/unitConverter/newUnits/UnitTest.java
delete mode 100644 src/org/unitConverter/newUnits/package-info.java
delete mode 100644 src/org/unitConverter/unit/AbstractUnit.java
create mode 100644 src/org/unitConverter/unit/BaseDimension.java
delete mode 100644 src/org/unitConverter/unit/DefaultUnitPrefix.java
delete mode 100644 src/org/unitConverter/unit/NonlinearUnits.java
delete mode 100644 src/org/unitConverter/unit/SIPrefix.java
create mode 100644 src/org/unitConverter/unit/UnitDatabase.java
create mode 100644 src/org/unitConverter/unit/UnitDatabaseTest.java
delete mode 100644 src/org/unitConverter/unit/UnitSystem.java
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
deleted file mode 100644
index 520195c..0000000
--- a/src/org/unitConverter/UnitsDatabase.java
+++ /dev/null
@@ -1,1658 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-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.Iterator;
-import java.util.List;
-import java.util.Map;
-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.dimension.UnitDimension;
-import org.unitConverter.math.DecimalComparison;
-import org.unitConverter.math.ExpressionParser;
-import org.unitConverter.unit.DefaultUnitPrefix;
-import org.unitConverter.unit.LinearUnit;
-import org.unitConverter.unit.SI;
-import org.unitConverter.unit.Unit;
-import org.unitConverter.unit.UnitPrefix;
-
-/**
- * A database of units, prefixes and dimensions, and their names.
- *
- * @author Adrien Hopkins
- * @since 2019-01-07
- * @since v0.1.0
- */
-public final class UnitsDatabase {
- /**
- * A map for units that allows the use of prefixes.
- *
- * 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.
- *
- *
- * The rules for applying prefixes onto units are the following:
- *
- * - Prefixes can only be applied to linear units.
- * - 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.
- * - 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".
- *
- *
- *
- * 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}.
- *
- *
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), {@link #containsValue} and {@link #values()}
- * currently ignore prefixes.
- *
- *
- * @author Adrien Hopkins
- * @since 2019-04-13
- * @since v0.2.0
- */
- private static final class PrefixedUnitMap implements Map {
- /**
- * The class used for entry sets.
- *
- *
- * 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.
- *
- *
- * @author Adrien Hopkins
- * @since 2019-04-13
- * @since v0.2.0
- */
- private static final class PrefixedUnitEntrySet extends AbstractSet> {
- /**
- * The entry for this set.
- *
- * @author Adrien Hopkins
- * @since 2019-04-14
- * @since v0.2.0
- */
- private static final class PrefixedUnitEntry implements Entry {
- 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> {
- // position in the unit list
- private int unitNamePosition = 0;
- // the indices of the prefixes attached to the current unit
- private final List prefixCoordinates = new ArrayList<>();
-
- // values from the unit entry set
- private final Map map;
- private transient final List unitNames;
- private transient final List 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 next() {
- // get next element
- final Entry 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 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 e) {
- throw new UnsupportedOperationException("Cannot add to an immutable set");
- }
-
- @Override
- public boolean addAll(final Collection extends Map.Entry> 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 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 tempEntry = (Entry) 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> 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> 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[] 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.
- *
- *
- * 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.
- *
- *
- * @author Adrien Hopkins
- * @since 2019-04-13
- * @since v0.2.0
- */
- private static final class PrefixedUnitNameSet extends AbstractSet {
- /**
- * 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 {
- // position in the unit list
- private int unitNamePosition = 0;
- // the indices of the prefixes attached to the current unit
- private final List prefixCoordinates = new ArrayList<>();
-
- // values from the unit name set
- private final Map map;
- private transient final List unitNames;
- private transient final List 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 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[] 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 units;
-
- /**
- * The available prefixes for use.
- *
- * @since 2019-04-13
- * @since v0.2.0
- */
- private final Map prefixes;
-
- // caches
- private transient Collection values = null;
- private transient Set keySet = null;
- private transient Set> 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 units, final Map 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}
- *
- *
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method only tests for prefixless units.
- *
- */
- @Override
- public boolean containsValue(final Object value) {
- return this.units.containsValue(value);
- }
-
- @Override
- public Set> 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 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}
- *
- *
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method ignores prefixes.
- *
- */
- @Override
- public Collection values() {
- if (this.values == null) {
- this.values = Collections.unmodifiableCollection(this.units.values());
- }
- return this.values;
- }
- }
-
- /**
- * 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.SI.getBaseUnit(UnitDimension.EMPTY))) {
- // 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 units in this system, excluding prefixes.
- *
- * @since 2019-01-07
- * @since v0.1.0
- */
- private final Map prefixlessUnits;
-
- /**
- * The unit prefixes in this system.
- *
- * @since 2019-01-14
- * @since v0.1.0
- */
- private final Map prefixes;
-
- /**
- * The dimensions in this system.
- *
- * @since 2019-03-14
- * @since v0.2.0
- */
- private final Map dimensions;
-
- /**
- * A map mapping strings to units (including prefixes)
- *
- * @since 2019-04-13
- * @since v0.2.0
- */
- private final Map units;
-
- /**
- * A parser that can parse unit expressions.
- *
- * @since 2019-03-22
- * @since v0.2.0
- */
- private final ExpressionParser 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("^", UnitsDatabase::exponentiateUnits, 2).build();
-
- /**
- * A parser that can parse unit prefix expressions
- *
- * @since 2019-04-13
- * @since v0.2.0
- */
- private final ExpressionParser 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 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 UnitsDatabase() {
- this.prefixlessUnits = new HashMap<>();
- this.prefixes = new HashMap<>();
- this.dimensions = new HashMap<>();
- this.units = new PrefixedUnitMap(this.prefixlessUnits, this.prefixes);
- }
-
- /**
- * 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 UnitDimension 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 UnitDimension 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 dimensionMap() {
- return Collections.unmodifiableMap(this.dimensions);
- }
-
- /**
- * Gets a unit dimension from the database using its name.
- *
- *
- * This method accepts exponents, like "L^3"
- *
- *
- * @param name
- * dimension's name
- * @return dimension
- * @since 2019-03-14
- * @since v0.2.0
- */
- public UnitDimension getDimension(final String name) {
- Objects.requireNonNull(name, "name must not be null.");
- if (name.contains("^")) {
- final String[] baseAndExponent = name.split("\\^");
-
- final UnitDimension 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
- *
- * The expression is a series of any of the following:
- *
- * - The name of a unit dimension, which multiplies or divides the result based on preceding operators
- * - The operators '*' and '/', which multiply and divide (note that just putting two unit dimensions next to each
- * other is equivalent to multiplication)
- * - The operator '^' which exponentiates. Exponents must be integers.
- *
- *
- * @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 UnitDimension 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;
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^");
-
- // fix broken spaces
- 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)
- if (name.contains("(") && name.contains(")")) {
- // break it into function name and value
- final List 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 unit.getBase().times(unit.convertToBase(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 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 new DefaultUnitPrefix(Double.parseDouble(name));
- } catch (final NumberFormatException e) {
- return this.prefixes.get(name);
- }
- }
-
- /**
- * Gets a unit prefix from a prefix expression
- *
- * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
- * another prefix
- *
- *
- * @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;
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
-
- return this.prefixExpressionParser.parseExpression(modifiedExpression);
- }
-
- /**
- * 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.SI.getBaseUnit(UnitDimension.EMPTY).times(value);
- } catch (final NumberFormatException e) {
- return this.units.get(name);
- }
-
- }
-
- /**
- * Uses the database's unit data to parse an expression into a unit
- *
- * The expression is a series of any of the following:
- *
- * - The name of a unit, which multiplies or divides the result based on preceding operators
- * - The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
- * other is equivalent to multiplication)
- * - The operator '^' which exponentiates. Exponents must be integers.
- * - A number which is multiplied or divided
- *
- * 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("-", " - ");
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
-
- // the previous operation breaks negative numbers, fix them!
- // (i.e. -2 becomes - 2)
- for (int i = 2; i < modifiedExpression.length(); i++) {
- if (modifiedExpression.charAt(i) == '-'
- && 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.
- *
- * Each line in the file should consist of a name and an expression (parsed by getDimensionFromExpression) separated
- * by any number of tab characters.
- *
- *
- * Allowed exceptions:
- *
- * - Anything after a '#' character is considered a comment and ignored.
- * - Blank lines are also ignored
- * - 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.
- *
- *
- * @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 File file) {
- Objects.requireNonNull(file, "file must not be null.");
- try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
- // while the reader has lines to read, read a line, then parse it, then add it
- long lineCounter = 0;
- while (reader.ready()) {
- this.addDimensionFromLine(reader.readLine(), ++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.
- *
- * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
- * any number of tab characters.
- *
- *
- * Allowed exceptions:
- *
- * - Anything after a '#' character is considered a comment and ignored.
- * - Blank lines are also ignored
- * - 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.
- *
- *
- * @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 File file) {
- Objects.requireNonNull(file, "file must not be null.");
- try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
- // while the reader has lines to read, read a line, then parse it, then add it
- long lineCounter = 0;
- while (reader.ready()) {
- this.addUnitOrPrefixFromLine(reader.readLine(), ++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 prefixMap() {
- return Collections.unmodifiableMap(this.prefixes);
- }
-
- @Override
- public String toString() {
- return String.format("Unit Database with %d units and %d unit prefixes", this.prefixlessUnits.size(),
- this.prefixes.size());
- }
-
- /**
- * Returns a map mapping unit names to units, including units with prefixes.
- *
- * 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}.
- *
- *
- * Specifically, the operations that will throw an IllegalStateException if the map is infinite in size are:
- *
- * - {@code unitMap.entrySet().toArray()} (either overloading)
- * - {@code unitMap.keySet().toArray()} (either overloading)
- *
- *
- *
- * 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.
- *
- *
- * @return a map mapping unit names to units, including prefixed names
- * @since 2019-04-13
- * @since v0.2.0
- */
- public Map 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 unitMapPrefixless() {
- return Collections.unmodifiableMap(this.prefixlessUnits);
- }
-}
diff --git a/src/org/unitConverter/UnitsDatabaseTest.java b/src/org/unitConverter/UnitsDatabaseTest.java
deleted file mode 100644
index c46d598..0000000
--- a/src/org/unitConverter/UnitsDatabaseTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.junit.jupiter.api.Test;
-import org.unitConverter.unit.DefaultUnitPrefix;
-import org.unitConverter.unit.LinearUnit;
-import org.unitConverter.unit.SI;
-import org.unitConverter.unit.Unit;
-import org.unitConverter.unit.UnitPrefix;
-
-/**
- * A test for the {@link UnitsDatabase} class. This is NOT part of this program's public API.
- *
- * @author Adrien Hopkins
- * @since 2019-04-14
- * @since v0.2.0
- */
-class UnitsDatabaseTest {
- // some linear units and one nonlinear
- private static final Unit U = SI.METRE;
- private static final Unit V = SI.KILOGRAM;
- private static final Unit W = SI.SECOND;
-
- // used for testing expressions
- // J = U^2 * V / W^2
- private static final LinearUnit J = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
- private static final LinearUnit K = SI.KELVIN;
-
- private static final Unit NONLINEAR = Unit.fromConversionFunctions(SI.METRE, o -> o + 1, o -> o - 1);
-
- // make the prefix values prime so I can tell which multiplications were made
- private static final UnitPrefix A = new DefaultUnitPrefix(2);
- private static final UnitPrefix B = new DefaultUnitPrefix(3);
- private static final UnitPrefix C = new DefaultUnitPrefix(5);
- private static final UnitPrefix AB = new DefaultUnitPrefix(7);
- private static final UnitPrefix BC = new DefaultUnitPrefix(11);
-
- /**
- * Confirms that operations that shouldn't function for infinite databases throw an {@code IllegalStateException}.
- *
- * @since 2019-05-03
- */
- @Test
- public void testInfiniteSetExceptions() {
- // load units
- final UnitsDatabase infiniteDatabase = new UnitsDatabase();
-
- infiniteDatabase.addUnit("J", J);
- infiniteDatabase.addUnit("K", K);
-
- infiniteDatabase.addPrefix("A", A);
- infiniteDatabase.addPrefix("B", B);
- infiniteDatabase.addPrefix("C", C);
-
- {
- boolean exceptionThrown = false;
- try {
- infiniteDatabase.unitMap().entrySet().toArray();
- } catch (final IllegalStateException e) {
- exceptionThrown = true;
- // pass!
- } finally {
- if (!exceptionThrown) {
- fail("No IllegalStateException thrown");
- }
- }
- }
-
- {
- boolean exceptionThrown = false;
- try {
- infiniteDatabase.unitMap().keySet().toArray();
- } catch (final IllegalStateException e) {
- exceptionThrown = true;
- // pass!
- } finally {
- if (!exceptionThrown) {
- fail("No IllegalStateException thrown");
- }
- }
- }
- }
-
- /**
- * Test that prefixes correctly apply to units.
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- @Test
- public void testPrefixes() {
- final UnitsDatabase database = new UnitsDatabase();
-
- database.addUnit("U", U);
- database.addUnit("V", V);
- database.addUnit("W", W);
-
- database.addPrefix("A", A);
- database.addPrefix("B", B);
- database.addPrefix("C", C);
-
- // get the product
- final Unit abcuNonlinear = database.getUnit("ABCU");
- assert abcuNonlinear instanceof LinearUnit;
-
- final LinearUnit abcu = (LinearUnit) abcuNonlinear;
- assertEquals(A.getMultiplier() * B.getMultiplier() * C.getMultiplier(), abcu.getConversionFactor(), 1e-15);
- }
-
- /**
- * Tests the functionnalites of the prefixless unit map.
- *
- *
- * The map should be an auto-updating view of the units in the database.
- *
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- @Test
- public void testPrefixlessUnitMap() {
- final UnitsDatabase database = new UnitsDatabase();
- final Map prefixlessUnits = database.unitMapPrefixless();
-
- database.addUnit("U", U);
- database.addUnit("V", V);
- database.addUnit("W", W);
-
- // this should work because the map should be an auto-updating view
- assertTrue(prefixlessUnits.containsKey("U"));
- assertFalse(prefixlessUnits.containsKey("Z"));
-
- assertTrue(prefixlessUnits.containsValue(U));
- assertFalse(prefixlessUnits.containsValue(NONLINEAR));
- }
-
- /**
- * Tests that the database correctly stores and retrieves units, ignoring prefixes.
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- @Test
- public void testPrefixlessUnits() {
- final UnitsDatabase database = new UnitsDatabase();
-
- database.addUnit("U", U);
- database.addUnit("V", V);
- database.addUnit("W", W);
-
- assertTrue(database.containsUnitName("U"));
- assertFalse(database.containsUnitName("Z"));
-
- assertEquals(U, database.getUnit("U"));
- assertEquals(null, database.getUnit("Z"));
- }
-
- /**
- * Test that unit expressions return the correct value.
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- @Test
- public void testUnitExpressions() {
- // load units
- final UnitsDatabase database = new UnitsDatabase();
-
- database.addUnit("U", U);
- database.addUnit("V", V);
- database.addUnit("W", W);
- database.addUnit("fj", J.times(5));
- database.addUnit("ej", J.times(8));
-
- database.addPrefix("A", A);
- database.addPrefix("B", B);
- database.addPrefix("C", C);
-
- // first test - test prefixes and operations
- final Unit expected1 = J.withPrefix(A).withPrefix(B).withPrefix(C).withPrefix(C);
- final Unit actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W");
-
- assertEquals(expected1, actual1);
-
- // second test - test addition and subtraction
- final Unit expected2 = J.times(58);
- final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej");
-
- assertEquals(expected2, actual2);
- }
-
- /**
- * Tests both the unit name iterator and the name-unit entry iterator
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- @Test
- public void testUnitIterator() {
- // load units
- final UnitsDatabase database = new UnitsDatabase();
-
- database.addUnit("J", J);
- database.addUnit("K", K);
-
- database.addPrefix("A", A);
- database.addPrefix("B", B);
- database.addPrefix("C", C);
-
- final int NUM_UNITS = database.unitMapPrefixless().size();
- final int NUM_PREFIXES = database.prefixMap().size();
-
- final Iterator nameIterator = database.unitMap().keySet().iterator();
- final Iterator> entryIterator = database.unitMap().entrySet().iterator();
-
- int expectedLength = 1;
- int unitsWithThisLengthSoFar = 0;
-
- // loop 1000 times
- for (int i = 0; i < 1000; i++) {
- // expected length of next
- if (unitsWithThisLengthSoFar >= NUM_UNITS * (int) Math.pow(NUM_PREFIXES, expectedLength - 1)) {
- expectedLength++;
- unitsWithThisLengthSoFar = 0;
- }
-
- // test that stuff is valid
- final String nextName = nameIterator.next();
- final Unit nextUnit = database.getUnit(nextName);
- final Entry nextEntry = entryIterator.next();
-
- assertEquals(expectedLength, nextName.length());
- assertEquals(nextName, nextEntry.getKey());
- assertEquals(nextUnit, nextEntry.getValue());
-
- unitsWithThisLengthSoFar++;
- }
-
- // test toString for consistency
- final String entryIteratorString = entryIterator.toString();
- for (int i = 0; i < 3; i++) {
- assertEquals(entryIteratorString, entryIterator.toString());
- }
-
- final String nameIteratorString = nameIterator.toString();
- for (int i = 0; i < 3; i++) {
- assertEquals(nameIteratorString, nameIterator.toString());
- }
- }
-
- /**
- * Determine, given a unit name that could mean multiple things, which meaning is chosen.
- *
- * For example, "ABCU" could mean "A-B-C-U", "AB-C-U", or "A-BC-U". In this case, "AB-C-U" is the correct choice.
- *
- *
- * @since 2019-04-14
- * @since v0.2.0
- */
- @Test
- public void testUnitPrefixCombinations() {
- // load units
- final UnitsDatabase database = new UnitsDatabase();
-
- database.addUnit("J", J);
-
- database.addPrefix("A", A);
- database.addPrefix("B", B);
- database.addPrefix("C", C);
- database.addPrefix("AB", AB);
- database.addPrefix("BC", BC);
-
- // test 1 - AB-C-J vs A-BC-J vs A-B-C-J
- final Unit expected1 = J.withPrefix(AB).withPrefix(C);
- final Unit actual1 = database.getUnit("ABCJ");
-
- assertEquals(expected1, actual1);
-
- // test 2 - ABC-J vs AB-CJ vs AB-C-J
- database.addUnit("CJ", J.times(13));
- database.addPrefix("ABC", new DefaultUnitPrefix(17));
-
- final Unit expected2 = J.times(17);
- final Unit actual2 = database.getUnit("ABCJ");
-
- assertEquals(expected2, actual2);
- }
-}
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 2d3d1a5..4598971 100644
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -42,14 +42,13 @@ import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
-import org.unitConverter.UnitsDatabase;
-import org.unitConverter.dimension.StandardDimensions;
-import org.unitConverter.dimension.UnitDimension;
-import org.unitConverter.unit.BaseUnit;
-import org.unitConverter.unit.NonlinearUnits;
+import org.unitConverter.math.ObjectProduct;
+import org.unitConverter.unit.BaseDimension;
+import org.unitConverter.unit.LinearUnit;
import org.unitConverter.unit.SI;
import org.unitConverter.unit.Unit;
import org.unitConverter.unit.UnitPrefix;
+import org.unitConverter.unit.UnitDatabase;
/**
* @author Adrien Hopkins
@@ -66,7 +65,7 @@ final class UnitConverterGUI {
* @since 2019-04-14
* @since v0.2.0
*/
- private static void addDefaults(final UnitsDatabase database) {
+ private static void addDefaults(final UnitDatabase database) {
database.addUnit("metre", SI.METRE);
database.addUnit("kilogram", SI.KILOGRAM);
database.addUnit("gram", SI.KILOGRAM.dividedBy(1000));
@@ -75,24 +74,24 @@ final class UnitConverterGUI {
database.addUnit("kelvin", SI.KELVIN);
database.addUnit("mole", SI.MOLE);
database.addUnit("candela", SI.CANDELA);
- database.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION));
- database.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY));
+ database.addUnit("bit", SI.BIT);
+ database.addUnit("unit", SI.ONE);
// nonlinear units - must be loaded manually
- database.addUnit("tempCelsius", NonlinearUnits.CELSIUS);
- database.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
+ database.addUnit("tempCelsius", SI.CELSIUS);
+ // database.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
// load initial dimensions
- database.addDimension("LENGTH", StandardDimensions.LENGTH);
- database.addDimension("MASS", StandardDimensions.MASS);
- database.addDimension("TIME", StandardDimensions.TIME);
- database.addDimension("TEMPERATURE", StandardDimensions.TEMPERATURE);
+ database.addDimension("LENGTH", SI.Dimensions.LENGTH);
+ database.addDimension("MASS", SI.Dimensions.MASS);
+ database.addDimension("TIME", SI.Dimensions.TIME);
+ database.addDimension("TEMPERATURE", SI.Dimensions.TEMPERATURE);
}
/** The presenter's associated view. */
private final View view;
/** The units known by the program. */
- private final UnitsDatabase database;
+ private final UnitDatabase database;
/** The names of all of the units */
private final List unitNames;
@@ -119,7 +118,7 @@ final class UnitConverterGUI {
this.view = view;
// load initial units
- this.database = new UnitsDatabase();
+ this.database = new UnitDatabase();
Presenter.addDefaults(this.database);
this.database.loadUnitsFile(new File("unitsfile.txt"));
@@ -155,7 +154,7 @@ final class UnitConverterGUI {
this.dimensionNames.sort(null); // sorts it using Comparable
// a Predicate that returns true iff the argument is a full base unit
- final Predicate isFullBase = unit -> unit instanceof BaseUnit && ((BaseUnit) unit).isFullBase();
+ final Predicate isFullBase = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase();
// print out unit counts
System.out.printf("Successfully loaded %d units with %d unit names (%d base units).%n",
@@ -359,7 +358,7 @@ final class UnitConverterGUI {
*/
public final boolean unitMatchesDimension(final String unitName, final String dimensionName) {
final Unit unit = this.database.getUnit(unitName);
- final UnitDimension dimension = this.database.getDimension(dimensionName);
+ final ObjectProduct dimension = this.database.getDimension(dimensionName);
return unit.getDimension().equals(dimension);
}
diff --git a/src/org/unitConverter/dimension/BaseDimension.java b/src/org/unitConverter/dimension/BaseDimension.java
deleted file mode 100644
index 5e3ddad..0000000
--- a/src/org/unitConverter/dimension/BaseDimension.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.dimension;
-
-/**
- * A base dimension that makes up {@code UnitDimension} objects.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-public interface BaseDimension {
- /**
- * @return the dimension's name
- * @since 2018-12-22
- * @since v0.1.0
- */
- String getName();
-
- /**
- * @return a short string (usually one character) that represents this base dimension
- * @since 2018-12-22
- * @since v0.1.0
- */
- String getSymbol();
-}
diff --git a/src/org/unitConverter/dimension/OtherBaseDimension.java b/src/org/unitConverter/dimension/OtherBaseDimension.java
deleted file mode 100644
index 8aea2b9..0000000
--- a/src/org/unitConverter/dimension/OtherBaseDimension.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.dimension;
-
-import java.util.Objects;
-
-/**
- * Non-SI base dimensions.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public enum OtherBaseDimension implements BaseDimension {
- INFORMATION("Info"), CURRENCY("$$");
-
- /** The dimension's symbol */
- private final String symbol;
-
- /**
- * Creates the {@code SIBaseDimension}.
- *
- * @param symbol
- * dimension's symbol
- * @since 2018-12-11
- * @since v0.1.0
- */
- private OtherBaseDimension(final String symbol) {
- this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
- }
-
- @Override
- public String getName() {
- return this.toString();
- }
-
- @Override
- public String getSymbol() {
- return this.symbol;
- }
-}
diff --git a/src/org/unitConverter/dimension/SIBaseDimension.java b/src/org/unitConverter/dimension/SIBaseDimension.java
deleted file mode 100644
index c459963..0000000
--- a/src/org/unitConverter/dimension/SIBaseDimension.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.dimension;
-
-import java.util.Objects;
-
-/**
- * The seven base dimensions that make up the SI.
- *
- * @author Adrien Hopkins
- * @since 2018-12-11
- * @since v0.1.0
- */
-public enum SIBaseDimension implements BaseDimension {
- LENGTH("L"), MASS("M"), TIME("T"), ELECTRIC_CURRENT("I"), TEMPERATURE("\u0398"), // u0398 is the theta symbol
- QUANTITY("N"), LUMINOUS_INTENSITY("J");
-
- /** The dimension's symbol */
- private final String symbol;
-
- /**
- * Creates the {@code SIBaseDimension}.
- *
- * @param symbol
- * dimension's symbol
- * @since 2018-12-11
- * @since v0.1.0
- */
- private SIBaseDimension(final String symbol) {
- this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
- }
-
- @Override
- public String getName() {
- return this.toString();
- }
-
- @Override
- public String getSymbol() {
- return this.symbol;
- }
-
-}
diff --git a/src/org/unitConverter/dimension/StandardDimensions.java b/src/org/unitConverter/dimension/StandardDimensions.java
deleted file mode 100644
index 4b1b814..0000000
--- a/src/org/unitConverter/dimension/StandardDimensions.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.dimension;
-
-/**
- * All of the dimensions that are used by the SI.
- *
- * @author Adrien Hopkins
- * @since 2018-12-11
- * @since v0.1.0
- */
-public final class StandardDimensions {
- // base dimensions
- public static final UnitDimension EMPTY = UnitDimension.EMPTY;
- public static final UnitDimension LENGTH = UnitDimension.getBase(SIBaseDimension.LENGTH);
- public static final UnitDimension MASS = UnitDimension.getBase(SIBaseDimension.MASS);
- public static final UnitDimension TIME = UnitDimension.getBase(SIBaseDimension.TIME);
- public static final UnitDimension ELECTRIC_CURRENT = UnitDimension.getBase(SIBaseDimension.ELECTRIC_CURRENT);
- public static final UnitDimension TEMPERATURE = UnitDimension.getBase(SIBaseDimension.TEMPERATURE);
- public static final UnitDimension QUANTITY = UnitDimension.getBase(SIBaseDimension.QUANTITY);
- public static final UnitDimension LUMINOUS_INTENSITY = UnitDimension.getBase(SIBaseDimension.LUMINOUS_INTENSITY);
- public static final UnitDimension INFORMATION = UnitDimension.getBase(OtherBaseDimension.INFORMATION);
- public static final UnitDimension CURRENCY = UnitDimension.getBase(OtherBaseDimension.CURRENCY);
- // derived dimensions without named SI units
- public static final UnitDimension AREA = LENGTH.times(LENGTH);
-
- public static final UnitDimension VOLUME = AREA.times(LENGTH);
- public static final UnitDimension VELOCITY = LENGTH.dividedBy(TIME);
- public static final UnitDimension ACCELERATION = VELOCITY.dividedBy(TIME);
- public static final UnitDimension WAVENUMBER = EMPTY.dividedBy(LENGTH);
- public static final UnitDimension MASS_DENSITY = MASS.dividedBy(VOLUME);
- public static final UnitDimension SURFACE_DENSITY = MASS.dividedBy(AREA);
- public static final UnitDimension SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
- public static final UnitDimension CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
- public static final UnitDimension MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
- public static final UnitDimension CONCENTRATION = QUANTITY.dividedBy(VOLUME);
- public static final UnitDimension MASS_CONCENTRATION = CONCENTRATION.times(MASS);
- public static final UnitDimension LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
- public static final UnitDimension REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
- public static final UnitDimension REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
- public static final UnitDimension ANGLE = LENGTH.dividedBy(LENGTH);
- public static final UnitDimension SOLID_ANGLE = AREA.dividedBy(AREA);
- // derived dimensions with named SI units
- public static final UnitDimension FREQUENCY = EMPTY.dividedBy(TIME);
-
- public static final UnitDimension FORCE = MASS.times(ACCELERATION);
- public static final UnitDimension ENERGY = FORCE.times(LENGTH);
- public static final UnitDimension POWER = ENERGY.dividedBy(TIME);
- public static final UnitDimension ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
- public static final UnitDimension VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
- public static final UnitDimension CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
- public static final UnitDimension ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
- public static final UnitDimension ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
- public static final UnitDimension MAGNETIC_FLUX = VOLTAGE.times(TIME);
- public static final UnitDimension MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
- public static final UnitDimension INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
- public static final UnitDimension LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
- public static final UnitDimension ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
- public static final UnitDimension SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
- public static final UnitDimension CATALYTIC_ACTIVITY = QUANTITY.dividedBy(TIME);
-
- // You may NOT get StandardDimensions instances!
- private StandardDimensions() {
- throw new AssertionError();
- }
-}
diff --git a/src/org/unitConverter/dimension/UnitDimension.java b/src/org/unitConverter/dimension/UnitDimension.java
deleted file mode 100644
index dbeaeff..0000000
--- a/src/org/unitConverter/dimension/UnitDimension.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.dimension;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * An object that represents what a unit measures, like length, mass, area, energy, etc.
- *
- * @author Adrien Hopkins
- * @since 2018-12-11
- * @since v0.1.0
- */
-public final class UnitDimension {
- /**
- * The unit dimension where every exponent is zero
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- public static final UnitDimension EMPTY = new UnitDimension(new HashMap<>());
-
- /**
- * Gets an UnitDimension that has 1 of a certain dimension and nothing else
- *
- * @param dimension
- * dimension to get
- * @return unit dimension
- * @since 2018-12-11
- * @since v0.1.0
- */
- public static final UnitDimension getBase(final BaseDimension dimension) {
- final Map map = new HashMap<>();
- map.put(dimension, 1);
- return new UnitDimension(map);
- }
-
- /**
- * The base dimensions that make up this dimension.
- *
- * @since 2018-12-11
- * @since v0.1.0
- */
- final Map exponents;
-
- /**
- * Creates the {@code UnitDimension}.
- *
- * @param exponents
- * base dimensions that make up this dimension
- * @since 2018-12-11
- * @since v0.1.0
- */
- private UnitDimension(final Map exponents) {
- this.exponents = new HashMap<>(exponents);
- }
-
- /**
- * Divides this dimension by another
- *
- * @param other
- * other dimension
- * @return quotient of two dimensions
- * @since 2018-12-11
- * @since v0.1.0
- */
- public UnitDimension dividedBy(final UnitDimension other) {
- final Map map = new HashMap<>(this.exponents);
-
- for (final BaseDimension key : other.exponents.keySet()) {
- if (map.containsKey(key)) {
- // add the dimensions
- map.put(key, map.get(key) - other.exponents.get(key));
- } else {
- map.put(key, -other.exponents.get(key));
- }
- }
- return new UnitDimension(map);
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (!(obj instanceof UnitDimension))
- return false;
- final UnitDimension other = (UnitDimension) obj;
-
- // anything with a value of 0 is equal to a nonexistent value
- for (final BaseDimension b : this.getBaseSet()) {
- if (this.exponents.get(b) != other.exponents.get(b))
- if (!(this.exponents.get(b) == 0 && !other.exponents.containsKey(b)))
- return false;
- }
- for (final BaseDimension b : other.getBaseSet()) {
- if (this.exponents.get(b) != other.exponents.get(b))
- if (!(other.exponents.get(b) == 0 && !this.exponents.containsKey(b)))
- return false;
- }
- return true;
- }
-
- /**
- * @return a set of all of the base dimensions with non-zero exponents that make up this dimension.
- * @since 2018-12-12
- * @since v0.1.0
- */
- public final Set getBaseSet() {
- final Set dimensions = new HashSet<>();
-
- // add all dimensions with a nonzero exponent - they shouldn't be there in the first place
- for (final BaseDimension dimension : this.exponents.keySet()) {
- if (!this.exponents.get(dimension).equals(0)) {
- dimensions.add(dimension);
- }
- }
-
- return dimensions;
- }
-
- /**
- * Gets the exponent for a specific dimension.
- *
- * @param dimension
- * dimension to check
- * @return exponent for that dimension
- * @since 2018-12-12
- * @since v0.1.0
- */
- public int getExponent(final BaseDimension dimension) {
- return this.exponents.getOrDefault(dimension, 0);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(this.exponents);
- }
-
- /**
- * @return true if this dimension is a base, i.e. it has one exponent of one and no other nonzero exponents
- * @since 2019-01-15
- * @since v0.1.0
- */
- public boolean isBase() {
- int oneCount = 0;
- boolean twoOrMore = false; // has exponents of 2 or more
- for (final BaseDimension b : this.getBaseSet()) {
- if (this.exponents.get(b) == 1) {
- oneCount++;
- } else if (this.exponents.get(b) != 0) {
- twoOrMore = true;
- }
- }
- return (oneCount == 0 || oneCount == 1) && !twoOrMore;
- }
-
- /**
- * Multiplies this dimension by another
- *
- * @param other
- * other dimension
- * @return product of two dimensions
- * @since 2018-12-11
- * @since v0.1.0
- */
- public UnitDimension times(final UnitDimension other) {
- final Map map = new HashMap<>(this.exponents);
-
- for (final BaseDimension key : other.exponents.keySet()) {
- if (map.containsKey(key)) {
- // add the dimensions
- map.put(key, map.get(key) + other.exponents.get(key));
- } else {
- map.put(key, other.exponents.get(key));
- }
- }
- return new UnitDimension(map);
- }
-
- /**
- * Returns this dimension, but to an exponent
- *
- * @param exp
- * exponent
- * @return result of exponientation
- * @since 2019-01-15
- * @since v0.1.0
- */
- public UnitDimension toExponent(final int exp) {
- final Map map = new HashMap<>(this.exponents);
- for (final BaseDimension key : this.exponents.keySet()) {
- map.put(key, this.getExponent(key) * exp);
- }
- return new UnitDimension(map);
- }
-
- @Override
- public String toString() {
- final List positiveStringComponents = new ArrayList<>();
- final List negativeStringComponents = new ArrayList<>();
-
- // for each base dimension that makes up this dimension, add it and its exponent
- for (final BaseDimension dimension : this.getBaseSet()) {
- final int exponent = this.exponents.get(dimension);
- if (exponent > 0) {
- positiveStringComponents.add(String.format("%s^%d", dimension.getSymbol(), exponent));
- } else if (exponent < 0) {
- negativeStringComponents.add(String.format("%s^%d", dimension.getSymbol(), -exponent));
- }
- }
-
- final String positiveString = positiveStringComponents.isEmpty() ? "1"
- : String.join(" ", positiveStringComponents);
- final String negativeString = negativeStringComponents.isEmpty() ? ""
- : " / " + String.join(" ", negativeStringComponents);
-
- return positiveString + negativeString;
- }
-}
diff --git a/src/org/unitConverter/dimension/UnitDimensionTest.java b/src/org/unitConverter/dimension/UnitDimensionTest.java
deleted file mode 100644
index 017e3d2..0000000
--- a/src/org/unitConverter/dimension/UnitDimensionTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.dimension;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.unitConverter.dimension.StandardDimensions.AREA;
-import static org.unitConverter.dimension.StandardDimensions.ENERGY;
-import static org.unitConverter.dimension.StandardDimensions.LENGTH;
-import static org.unitConverter.dimension.StandardDimensions.MASS;
-import static org.unitConverter.dimension.StandardDimensions.MASS_DENSITY;
-import static org.unitConverter.dimension.StandardDimensions.QUANTITY;
-import static org.unitConverter.dimension.StandardDimensions.TIME;
-import static org.unitConverter.dimension.StandardDimensions.VOLUME;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests for {@link UnitDimension}. This is NOT part of this program's public API.
- *
- * @author Adrien Hopkins
- * @since 2018-12-12
- * @since v0.1.0
- */
-class UnitDimensionTest {
- /**
- * Tests {@link UnitDimension#equals}
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- @Test
- public void testEquals() {
- assertEquals(LENGTH, LENGTH);
- assertFalse(LENGTH.equals(QUANTITY));
- }
-
- /**
- * Tests {@code UnitDimension}'s exponentiation
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- @Test
- public void testExponents() {
- assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
- assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
- }
-
- /**
- * Tests {@code UnitDimension}'s multiplication and division.
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- @Test
- public void testMultiplicationAndDivision() {
- assertEquals(AREA, LENGTH.times(LENGTH));
- assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME));
- assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME));
- assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME));
- }
-}
diff --git a/src/org/unitConverter/dimension/package-info.java b/src/org/unitConverter/dimension/package-info.java
deleted file mode 100644
index 8cb26b1..0000000
--- a/src/org/unitConverter/dimension/package-info.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * 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 .
- */
-/**
- * Everything to do with what a unit measures, or its dimension.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-package org.unitConverter.dimension;
\ No newline at end of file
diff --git a/src/org/unitConverter/math/ObjectProductTest.java b/src/org/unitConverter/math/ObjectProductTest.java
index 03c767c..afd18b7 100644
--- a/src/org/unitConverter/math/ObjectProductTest.java
+++ b/src/org/unitConverter/math/ObjectProductTest.java
@@ -18,18 +18,17 @@ package org.unitConverter.math;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.unitConverter.dimension.StandardDimensions.AREA;
-import static org.unitConverter.dimension.StandardDimensions.ENERGY;
-import static org.unitConverter.dimension.StandardDimensions.LENGTH;
-import static org.unitConverter.dimension.StandardDimensions.MASS;
-import static org.unitConverter.dimension.StandardDimensions.MASS_DENSITY;
-import static org.unitConverter.dimension.StandardDimensions.QUANTITY;
-import static org.unitConverter.dimension.StandardDimensions.TIME;
-import static org.unitConverter.dimension.StandardDimensions.VOLUME;
+import static org.unitConverter.unit.SI.Dimensions.AREA;
+import static org.unitConverter.unit.SI.Dimensions.ENERGY;
+import static org.unitConverter.unit.SI.Dimensions.LENGTH;
+import static org.unitConverter.unit.SI.Dimensions.MASS;
+import static org.unitConverter.unit.SI.Dimensions.MASS_DENSITY;
+import static org.unitConverter.unit.SI.Dimensions.QUANTITY;
+import static org.unitConverter.unit.SI.Dimensions.TIME;
+import static org.unitConverter.unit.SI.Dimensions.VOLUME;
import org.junit.jupiter.api.Test;
-import org.unitConverter.dimension.SIBaseDimension;
-import org.unitConverter.dimension.UnitDimension;
+import org.unitConverter.unit.SI;
/**
* Tests for {@link ObjectProduct} using BaseDimension as a test object. This is NOT part of this program's public API.
@@ -59,8 +58,8 @@ class ObjectProductTest {
*/
@Test
public void testExponents() {
- assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
- assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
+ assertEquals(1, LENGTH.getExponent(SI.BaseDimensions.LENGTH));
+ assertEquals(3, VOLUME.getExponent(SI.BaseDimensions.LENGTH));
}
/**
diff --git a/src/org/unitConverter/newUnits/BaseDimension.java b/src/org/unitConverter/newUnits/BaseDimension.java
deleted file mode 100644
index a1cde46..0000000
--- a/src/org/unitConverter/newUnits/BaseDimension.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-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);
- }
-
- private final String name;
- 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/org/unitConverter/newUnits/BaseUnit.java b/src/org/unitConverter/newUnits/BaseUnit.java
deleted file mode 100644
index 6a57faa..0000000
--- a/src/org/unitConverter/newUnits/BaseUnit.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-import java.util.Objects;
-
-/**
- * A unit that other units are defined by.
- *
- * @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);
- }
-
- private final BaseDimension dimension;
- private final String name;
- private final String symbol;
-
- /**
- * Creates the {@code BaseUnit}.
- *
- * @param dimension
- * dimension of unit
- * @param name
- * 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 name, final String symbol) {
- super();
- this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
- this.name = Objects.requireNonNull(name, "name must not be null.");
- this.symbol = Objects.requireNonNull(symbol, "symbol 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
- public double convertFromBase(final double value) {
- return value;
- }
-
- @Override
- public double convertToBase(final double value) {
- return value;
- }
-
- /**
- * @return dimension
- * @since 2019-10-16
- */
- public final BaseDimension getBaseDimension() {
- return this.dimension;
- }
-
- /**
- * @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/org/unitConverter/newUnits/FunctionalUnit.java b/src/org/unitConverter/newUnits/FunctionalUnit.java
deleted file mode 100644
index 6bff3e8..0000000
--- a/src/org/unitConverter/newUnits/FunctionalUnit.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-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 {
- /**
- * Returns a unit from its base and the functions it uses to convert to and from its base.
- *
- * @param base
- * unit's base
- * @param converterFrom
- * function that accepts a value expressed in the unit's base and returns that value expressed in this
- * unit.
- * @param converterTo
- * function that accepts a value expressed in the unit and returns that value expressed in the unit's
- * base.
- * @return a unit that uses the provided functions to convert.
- * @since 2019-05-22
- * @throws NullPointerException
- * if any argument is null
- */
- public static FunctionalUnit valueOf(final ObjectProduct base, final DoubleUnaryOperator converterFrom,
- final DoubleUnaryOperator converterTo) {
- return new FunctionalUnit(base, converterFrom, converterTo);
- }
-
- /**
- * 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
- */
- private FunctionalUnit(final ObjectProduct base, final DoubleUnaryOperator converterFrom,
- final DoubleUnaryOperator converterTo) {
- super(base);
- this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null.");
- this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null.");
- }
-
- @Override
- public double convertFromBase(final double value) {
- return this.converterFrom.applyAsDouble(value);
- }
-
- @Override
- public double convertToBase(final double value) {
- return this.converterTo.applyAsDouble(value);
- }
-
-}
diff --git a/src/org/unitConverter/newUnits/LinearUnit.java b/src/org/unitConverter/newUnits/LinearUnit.java
deleted file mode 100644
index c8c610e..0000000
--- a/src/org/unitConverter/newUnits/LinearUnit.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-import java.util.Objects;
-
-import org.unitConverter.math.DecimalComparison;
-import org.unitConverter.math.ObjectProduct;
-
-/**
- * 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
- */
- public static LinearUnit fromUnitValue(final Unit unit, final double value) {
- return new LinearUnit(unit.getBase(), unit.convertToBase(value));
- }
-
- /**
- * 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
- */
- public static LinearUnit valueOf(final ObjectProduct unitBase, final double conversionFactor) {
- return new LinearUnit(unitBase, conversionFactor);
- }
-
- /**
- * The value of this unit as represented in its base form. Mathematically,
- *
- *
- * this = conversionFactor * getBase()
- *
- *
- * @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 unitBase, final double conversionFactor) {
- super(unitBase);
- this.conversionFactor = conversionFactor;
- }
-
- @Override
- protected double convertFromBase(final double value) {
- return value / this.getConversionFactor();
- }
-
- @Override
- protected double convertToBase(final double value) {
- return value * 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 base = this.getBase().dividedBy(divisor.getBase());
- return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor());
- }
-
- @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;
- }
-
- @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.
- *
- * Two units can be subtracted if they have the same base. If {@code subtrahend} does not meet this condition, an
- * {@code IllegalArgumentException} will be thrown.
- *
- *
- * @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 subtrahendend) {
- Objects.requireNonNull(subtrahendend, "addend must not be null.");
-
- // reject subtrahends that cannot be added to this unit
- if (!this.getBase().equals(subtrahendend.getBase()))
- throw new IllegalArgumentException(
- String.format("Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahendend));
-
- // add the units
- return valueOf(this.getBase(), this.getConversionFactor() - subtrahendend.getConversionFactor());
- }
-
- /**
- * Returns the sum of this unit and another.
- *
- * Two units can be added if they have the same base. If {@code addend} does not meet this condition, an
- * {@code IllegalArgumentException} will be thrown.
- *
- *
- * @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 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));
- }
-
- // returns a definition of the unit
- @Override
- public String toString() {
- return Double.toString(this.conversionFactor) + " * " + this.getBase().toString(BaseUnit::getSymbol);
- }
-
- /**
- * Returns the result of applying {@code prefix} to this unit.
- *
- * @param prefix
- * prefix to apply
- * @return unit with prefix
- * @since 2019-03-18
- * @since v0.2.0
- */
- public LinearUnit withPrefix(final UnitPrefix prefix) {
- return this.times(prefix.getMultiplier());
- }
-}
diff --git a/src/org/unitConverter/newUnits/SI.java b/src/org/unitConverter/newUnits/SI.java
deleted file mode 100644
index b7a117a..0000000
--- a/src/org/unitConverter/newUnits/SI.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-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.
- *
- *
- * This class does not include prefixed units. To obtain prefixed units, use {@link LinearUnit#withPrefix}:
- *
- *
- * LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
- *
- *
- *
- * @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", "$");
-
- // You may NOT get SI.BaseUnits instances!
- private BaseUnits() {
- throw new AssertionError();
- }
- }
-
- // dimensions used in the SI, as ObjectProducts
- public static final class Dimensions {
- public static final ObjectProduct EMPTY = ObjectProduct.empty();
- public static final ObjectProduct LENGTH = ObjectProduct.oneOf(BaseDimensions.LENGTH);
- public static final ObjectProduct MASS = ObjectProduct.oneOf(BaseDimensions.MASS);
- public static final ObjectProduct TIME = ObjectProduct.oneOf(BaseDimensions.TIME);
- public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct
- .oneOf(BaseDimensions.ELECTRIC_CURRENT);
- public static final ObjectProduct TEMPERATURE = ObjectProduct.oneOf(BaseDimensions.TEMPERATURE);
- public static final ObjectProduct QUANTITY = ObjectProduct.oneOf(BaseDimensions.QUANTITY);
- public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct
- .oneOf(BaseDimensions.LUMINOUS_INTENSITY);
- public static final ObjectProduct INFORMATION = ObjectProduct.oneOf(BaseDimensions.INFORMATION);
- public static final ObjectProduct CURRENCY = ObjectProduct.oneOf(BaseDimensions.CURRENCY);
- // derived dimensions without named SI units
- public static final ObjectProduct AREA = LENGTH.times(LENGTH);
-
- public static final ObjectProduct VOLUME = AREA.times(LENGTH);
- public static final ObjectProduct VELOCITY = LENGTH.dividedBy(TIME);
- public static final ObjectProduct ACCELERATION = VELOCITY.dividedBy(TIME);
- public static final ObjectProduct WAVENUMBER = EMPTY.dividedBy(LENGTH);
- public static final ObjectProduct MASS_DENSITY = MASS.dividedBy(VOLUME);
- public static final ObjectProduct SURFACE_DENSITY = MASS.dividedBy(AREA);
- public static final ObjectProduct SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
- public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
- public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
- public static final ObjectProduct CONCENTRATION = QUANTITY.dividedBy(VOLUME);
- public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION.times(MASS);
- public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
- public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
- public static final ObjectProduct REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
- public static final ObjectProduct ANGLE = LENGTH.dividedBy(LENGTH);
- public static final ObjectProduct SOLID_ANGLE = AREA.dividedBy(AREA);
- // derived dimensions with named SI units
- public static final ObjectProduct FREQUENCY = EMPTY.dividedBy(TIME);
-
- public static final ObjectProduct FORCE = MASS.times(ACCELERATION);
- public static final ObjectProduct ENERGY = FORCE.times(LENGTH);
- public static final ObjectProduct POWER = ENERGY.dividedBy(TIME);
- public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
- public static final ObjectProduct VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
- public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
- public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
- public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
- public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE.times(TIME);
- public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
- public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
- public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
- public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
- public static final ObjectProduct SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
- public static final ObjectProduct 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();
- public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit();
- public static final LinearUnit SECOND = BaseUnits.SECOND.asLinearUnit();
- public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit();
- public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit();
- public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit();
- public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit();
- public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit();
- public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit();
-
- // Non-base units
- public static final LinearUnit RADIAN = METRE.dividedBy(METRE);
- public static final LinearUnit STERADIAN = RADIAN.times(RADIAN);
- public static final LinearUnit HERTZ = ONE.dividedBy(SECOND); // for periodic phenomena
- public static final LinearUnit NEWTON = KILOGRAM.times(METRE).dividedBy(SECOND.times(SECOND));
- public static final LinearUnit PASCAL = NEWTON.dividedBy(METRE.times(METRE));
- public static final LinearUnit JOULE = NEWTON.times(METRE);
- public static final LinearUnit WATT = JOULE.dividedBy(SECOND);
- public static final LinearUnit COULOMB = AMPERE.times(SECOND);
- public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB);
- public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT);
- public static final LinearUnit OHM = VOLT.dividedBy(AMPERE);
- public static final LinearUnit SIEMENS = ONE.dividedBy(OHM);
- public static final LinearUnit WEBER = VOLT.times(SECOND);
- public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE));
- public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE);
- public static final LinearUnit LUMEN = CANDELA.times(STERADIAN);
- public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE));
- public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND); // for activity referred to a nucleotide
- public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM); // for absorbed dose
- public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM); // for dose equivalent
- public static final LinearUnit KATAL = MOLE.dividedBy(SECOND);
-
- // Non-SI units included for convenience
- public static final Unit CELSIUS = Unit.fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15,
- tempC -> tempC + 273.15);
- public static final LinearUnit MINUTE = SECOND.times(60);
- public static final LinearUnit HOUR = MINUTE.times(60);
- public static final LinearUnit DAY = HOUR.times(60);
- public static final LinearUnit DEGREE = RADIAN.times(360 / (2 * Math.PI));
- public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60);
- public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60);
- public static final LinearUnit ASTRONOMICAL_UNIT = METRE.times(149597870700.0);
- public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT.times(ARCSECOND);
- public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0);
- public static final LinearUnit LITRE = METRE.times(METRE).times(METRE).dividedBy(1000.0);
- public static final LinearUnit TONNE = KILOGRAM.times(1000.0);
- public static final LinearUnit DALTON = KILOGRAM.times(1.660539040e-27); // approximate value
- public static final LinearUnit ELECTRONVOLT = JOULE.times(1.602176634e-19);
- public static final Unit NEPER = Unit.fromConversionFunctions(ONE.getBase(), pr -> 0.5 * Math.log(pr),
- Np -> Math.exp(2 * Np));
- public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(), pr -> Math.log10(pr),
- dB -> Math.pow(10, dB));
- public static final Unit DECIBEL = Unit.fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr),
- dB -> Math.pow(10, dB / 10));
-
- /// The prefixes of the SI
- // expanding decimal prefixes
- public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3);
- public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6);
- public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9);
- public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12);
- public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15);
- public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18);
- public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21);
- public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24);
-
- // contracting decimal prefixes
- public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3);
- public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6);
- public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9);
- public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12);
- public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15);
- public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18);
- public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21);
- public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24);
-
- // prefixes that don't match the pattern of thousands
- public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1);
- public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2);
- public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1);
- public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2);
- public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024);
- public static final UnitPrefix MEBI = KIBI.times(1024);
- public static final UnitPrefix GIBI = MEBI.times(1024);
- public static final UnitPrefix TEBI = GIBI.times(1024);
- public static final UnitPrefix PEBI = TEBI.times(1024);
- public static final UnitPrefix EXBI = PEBI.times(1024);
-
- // You may NOT get SI instances!
- private SI() {
- throw new AssertionError();
- }
-}
diff --git a/src/org/unitConverter/newUnits/Unit.java b/src/org/unitConverter/newUnits/Unit.java
deleted file mode 100644
index 339ab95..0000000
--- a/src/org/unitConverter/newUnits/Unit.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.DoubleUnaryOperator;
-
-import org.unitConverter.math.ObjectProduct;
-
-/**
- * A unit that is composed of base units.
- *
- * @author Adrien Hopkins
- * @since 2019-10-16
- */
-public abstract class Unit {
- /**
- * Returns a unit from its base and the functions it uses to convert to and from its base.
- *
- *
- * 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);}
- *
- *
- * @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 Unit fromConversionFunctions(final ObjectProduct base,
- final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo) {
- return FunctionalUnit.valueOf(base, converterFrom, converterTo);
- }
-
- /**
- * The combination of units that this unit is based on.
- *
- * @since 2019-10-16
- */
- private final ObjectProduct unitBase;
-
- /**
- * Cache storing the result of getDimension()
- *
- * @since 2019-10-16
- */
- private transient ObjectProduct dimension = null;
-
- /**
- * A constructor that constructs {@code BaseUnit} instances.
- *
- * @since 2019-10-16
- */
- Unit() {
- if (this instanceof BaseUnit) {
- this.unitBase = ObjectProduct.oneOf((BaseUnit) this);
- } else
- throw new AssertionError();
- }
-
- /**
- * Creates the {@code AbstractUnit}.
- *
- * @param unitBase
- * @since 2019-10-16
- * @throws NullPointerException
- * if unitBase is null
- */
- protected Unit(final ObjectProduct unitBase) {
- this.unitBase = Objects.requireNonNull(unitBase, "unitBase must not be null.");
- }
-
- /**
- * Checks if a value expressed in this unit can be converted to a value expressed in {@code other}
- *
- * @param other
- * unit to test with
- * @return true if the units are compatible
- * @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());
- }
-
- /**
- * Converts from a value expressed in this unit's base unit to a value expressed in this unit.
- *
- * 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.
- *
- *
- * If this unit is a base unit, this method should return {@code value}.
- *
- *
- * @param value
- * value expressed in base unit
- * @return value expressed in this 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}.
- *
- * @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 IUnit#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 from a value expressed in this unit to a value expressed in this unit's base unit.
- *
- * 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.
- *
- *
- * If this unit is a base unit, this method should return {@code value}.
- *
- *
- * @param value
- * value expressed in this unit
- * @return value expressed in base 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 getBase() {
- return this.unitBase;
- }
-
- /**
- * @return dimension measured by this unit
- * @since 2018-12-22
- * @since v0.1.0
- */
- public final ObjectProduct getDimension() {
- if (this.dimension == null) {
- final Map mapping = this.unitBase.exponentMap();
- final Map dimensionMap = new HashMap<>();
-
- for (final BaseUnit key : mapping.keySet()) {
- dimensionMap.put(key.getBaseDimension(), mapping.get(key));
- }
-
- this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
- }
- return this.dimension;
- }
-
- @Override
- public String toString() {
- return "Unit derived from base " + this.getBase().toString();
- }
-}
diff --git a/src/org/unitConverter/newUnits/UnitPrefix.java b/src/org/unitConverter/newUnits/UnitPrefix.java
deleted file mode 100644
index 5608098..0000000
--- a/src/org/unitConverter/newUnits/UnitPrefix.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-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);
- }
-
- /**
- * 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) {
- this.multiplier = multiplier;
- }
-
- /**
- * 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());
- }
-
- @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());
- }
-
- public double getMultiplier() {
- return this.multiplier;
- }
-
- @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));
- }
-
- @Override
- public String toString() {
- return String.format("Unit prefix equal to %s", this.multiplier);
- }
-}
diff --git a/src/org/unitConverter/newUnits/UnitTest.java b/src/org/unitConverter/newUnits/UnitTest.java
deleted file mode 100644
index 33bd264..0000000
--- a/src/org/unitConverter/newUnits/UnitTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.newUnits;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.util.Random;
-import java.util.concurrent.ThreadLocalRandom;
-
-import org.junit.jupiter.api.Test;
-import org.unitConverter.math.DecimalComparison;
-
-/**
- * Testing the various Unit classes. This is NOT part of this program's public API.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-class UnitTest {
- /** A random number generator */
- private static final Random rng = ThreadLocalRandom.current();
-
- @Test
- public void testAdditionAndSubtraction() {
- final LinearUnit inch = SI.METRE.times(0.0254);
- final LinearUnit foot = SI.METRE.times(0.3048);
-
- assertEquals(inch.plus(foot), SI.METRE.times(0.3302));
- assertEquals(foot.minus(inch), SI.METRE.times(0.2794));
- }
-
- @Test
- public void testConversion() {
- final LinearUnit metre = SI.METRE;
- final Unit inch = metre.times(0.0254);
-
- assertEquals(1.9, inch.convertTo(metre, 75), 0.01);
-
- // try random stuff
- for (int i = 0; i < 1000; i++) {
- // initiate random values
- final double conversionFactor = rng.nextDouble() * 1000000;
- final double testValue = rng.nextDouble() * 1000000;
- final double expected = testValue * conversionFactor;
-
- // test
- final Unit unit = SI.METRE.times(conversionFactor);
- final double actual = unit.convertToBase(testValue);
-
- assertEquals(actual, expected, expected * DecimalComparison.DOUBLE_EPSILON);
- }
- }
-
- @Test
- public void testEquals() {
- final LinearUnit metre = SI.METRE;
- final Unit meter = SI.BaseUnits.METRE.asLinearUnit();
-
- assertEquals(metre, meter);
- }
-
- @Test
- public void testMultiplicationAndDivision() {
- // test unit-times-unit multiplication
- final LinearUnit generatedJoule = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
- final LinearUnit actualJoule = SI.JOULE;
-
- assertEquals(generatedJoule, actualJoule);
-
- // test multiplication by conversion factors
- final LinearUnit kilometre = SI.METRE.times(1000);
- final LinearUnit hour = SI.SECOND.times(3600);
- final LinearUnit generatedKPH = kilometre.dividedBy(hour);
-
- final LinearUnit actualKPH = SI.METRE.dividedBy(SI.SECOND).dividedBy(3.6);
-
- assertEquals(generatedKPH, actualKPH);
- }
-
- @Test
- public void testPrefixes() {
- final LinearUnit generatedKilometre = SI.METRE.withPrefix(SI.KILO);
- final LinearUnit actualKilometre = SI.METRE.times(1000);
-
- assertEquals(generatedKilometre, actualKilometre);
- }
-}
diff --git a/src/org/unitConverter/newUnits/package-info.java b/src/org/unitConverter/newUnits/package-info.java
deleted file mode 100644
index 9cd0d1a..0000000
--- a/src/org/unitConverter/newUnits/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * 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 .
- */
-/**
- * The new definition for units.
- *
- * @author Adrien Hopkins
- * @since 2019-10-16
- */
-package org.unitConverter.newUnits;
\ No newline at end of file
diff --git a/src/org/unitConverter/unit/AbstractUnit.java b/src/org/unitConverter/unit/AbstractUnit.java
deleted file mode 100644
index 6045127..0000000
--- a/src/org/unitConverter/unit/AbstractUnit.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.unit;
-
-import java.util.Objects;
-
-import org.unitConverter.dimension.UnitDimension;
-
-/**
- * The default abstract implementation of the {@code Unit} interface.
- *
- *
- * With the addition of {@link Unit#fromConversionFunctions}, there is no longer any reason to use {@code AbstractUnit}
- * for any purpose other than making subclasses. Units should never be declared as {@code AbstractUnit}, they should be
- * declared as {@code Unit}. Now that {@code Unit.fromConversionFunctions} exists, it is preferred to creating anonymous
- * inner types of {@code AbstractUnit}.
- *
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-public abstract class AbstractUnit implements Unit {
- /**
- * The dimension, or what the unit measures.
- *
- * @since 2018-12-22
- * @since v0.1.0
- */
- private final UnitDimension dimension;
-
- /**
- * The unit's base unit. Values converted by {@code convertFromBase} and {@code convertToBase} are expressed in this
- * unit.
- *
- * @since 2018-12-22
- * @since v0.1.0
- */
- private final BaseUnit base;
-
- /**
- * The system that this unit is a part of.
- *
- * @since 2018-12-23
- * @since v0.1.0
- */
- private final UnitSystem system;
-
- /**
- * Creates the {@code AbstractUnit}.
- *
- * @param base
- * unit's base
- * @throws NullPointerException
- * if name, symbol or base is null
- * @since 2018-12-22
- * @since v0.1.0
- */
- public AbstractUnit(final BaseUnit base) {
- this.base = Objects.requireNonNull(base, "base must not be null.");
- this.dimension = this.base.getDimension();
- this.system = this.base.getSystem();
- }
-
- /**
- * Creates the {@code AbstractUnit} using a unique dimension. This constructor is for making base units and should
- * only be used by {@code BaseUnit}.
- *
- * @param dimension
- * dimension measured by unit
- * @param system
- * system that unit is a part of
- * @throws AssertionError
- * if this constructor is not run by {@code BaseUnit} or a subclass
- * @throws NullPointerException
- * if name, symbol or dimension is null
- * @since 2018-12-23
- * @since v0.1.0
- */
- AbstractUnit(final UnitDimension dimension, final UnitSystem system) {
- // try to set this as a base unit
- if (this instanceof BaseUnit) {
- this.base = (BaseUnit) this;
- } else
- throw new AssertionError();
-
- this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
- this.system = Objects.requireNonNull(system, "system must not be null.");
- }
-
- @Override
- public final BaseUnit getBase() {
- return this.base;
- }
-
- @Override
- public final UnitDimension getDimension() {
- return this.dimension;
- }
-
- @Override
- public final UnitSystem getSystem() {
- return this.system;
- }
-
- @Override
- public String toString() {
- return String.format("%s-derived unit of dimension %s", this.getSystem(), this.getDimension());
- }
-}
diff --git a/src/org/unitConverter/unit/BaseDimension.java b/src/org/unitConverter/unit/BaseDimension.java
new file mode 100644
index 0000000..35acd18
--- /dev/null
+++ b/src/org/unitConverter/unit/BaseDimension.java
@@ -0,0 +1,81 @@
+/**
+ * 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 .
+ */
+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);
+ }
+
+ private final String name;
+ 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/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
index 67309cf..8f44861 100644
--- a/src/org/unitConverter/unit/BaseUnit.java
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 Adrien Hopkins
+ * 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
@@ -18,151 +18,100 @@ package org.unitConverter.unit;
import java.util.Objects;
-import org.unitConverter.dimension.StandardDimensions;
-import org.unitConverter.dimension.UnitDimension;
-
/**
- * A unit that is the base for its dimension. It does not have to be for a base dimension, so units like the Newton and
- * Joule are still base units.
- *
- * {@code BaseUnit} does not have any public constructors or static factories. There are two ways to obtain
- * {@code BaseUnit} instances.
- *
- * - The class {@link SI} in this package has constants for all of the SI base units. You can use these constants and
- * multiply or divide them to get other units. For example:
- *
- *
- * BaseUnit JOULE = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
- *
- *
- *
- * - You can also query a unit system for a base unit using a unit dimension. The previously mentioned {@link SI}
- * class can do this for SI and SI-derived units (including imperial and USC), but if you want to use another system,
- * this is the way to do it. {@link StandardDimensions} contains common unit dimensions that you can use for this. Here
- * is an example:
- *
- *
- * BaseUnit JOULE = SI.SI.getBaseUnit(StandardDimensions.ENERGY);
- *
- *
- *
- *
+ * A unit that other units are defined by.
*
* @author Adrien Hopkins
- * @since 2018-12-23
- * @since v0.1.0
+ * @since 2019-10-16
*/
-public final class BaseUnit extends LinearUnit {
+public final class BaseUnit extends Unit {
/**
- * Is this unit a full base (i.e. m, s, ... but not N, J, ...)
+ * Gets a base unit from the dimension it measures, its name and its symbol.
*
- * @since 2019-01-15
- * @since v0.1.0
+ * @param dimension
+ * dimension measured by this unit
+ * @param name
+ * name of unit
+ * @param symbol
+ * symbol of unit
+ * @return base unit
+ * @since 2019-10-16
*/
- private final boolean isFullBase;
+ public static BaseUnit valueOf(final BaseDimension dimension, final String name, final String symbol) {
+ return new BaseUnit(dimension, name, symbol);
+ }
+
+ private final BaseDimension dimension;
+ private final String name;
+ private final String symbol;
/**
* Creates the {@code BaseUnit}.
*
* @param dimension
- * dimension measured by unit
- * @param system
- * system that unit is a part of
+ * dimension of unit
* @param name
* name of unit
* @param symbol
* symbol of unit
- * @since 2018-12-23
- * @since v0.1.0
+ * @throws NullPointerException
+ * if any argument is null
+ * @since 2019-10-16
*/
- BaseUnit(final UnitDimension dimension, final UnitSystem system) {
- super(dimension, system, 1);
- this.isFullBase = dimension.isBase();
+ private BaseUnit(final BaseDimension dimension, final String name, final String symbol) {
+ super();
+ this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
+ this.name = Objects.requireNonNull(name, "name must not be null.");
+ this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
}
/**
- * Returns the quotient of this unit and another.
- *
- * Two units can be divided if they are part of the same unit system. If {@code divisor} does not meet this
- * condition, an {@code IllegalArgumentException} should be thrown.
- *
+ * 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.
*
- * @param divisor
- * unit to divide by
- * @return quotient of two units
- * @throws IllegalArgumentException
- * if {@code divisor} is not compatible for division as described above
- * @throws NullPointerException
- * if {@code divisor} is null
- * @since 2018-12-22
- * @since v0.1.0
+ * @return this unit as a {@code LinearUnit}
+ * @since 2019-10-16
*/
- public BaseUnit dividedBy(final BaseUnit divisor) {
- Objects.requireNonNull(divisor, "other must not be null.");
+ public LinearUnit asLinearUnit() {
+ return LinearUnit.valueOf(this.getBase(), 1);
+ }
- // check that these units can be multiplied
- if (!this.getSystem().equals(divisor.getSystem()))
- throw new IllegalArgumentException(
- String.format("Incompatible units for division \"%s\" and \"%s\".", this, divisor));
+ @Override
+ public double convertFromBase(final double value) {
+ return value;
+ }
- return new BaseUnit(this.getDimension().dividedBy(divisor.getDimension()), this.getSystem());
+ @Override
+ public double convertToBase(final double value) {
+ return value;
}
/**
- * @return true if the unit is a "full base" unit like the metre or second.
- * @since 2019-04-10
- * @since v0.2.0
+ * @return dimension
+ * @since 2019-10-16
*/
- public final boolean isFullBase() {
- return this.isFullBase;
+ public final BaseDimension getBaseDimension() {
+ return this.dimension;
}
/**
- * Returns the product of this unit and another.
- *
- * Two units can be multiplied if they are part of the same unit system. If {@code multiplier} does not meet this
- * condition, an {@code IllegalArgumentException} should be thrown.
- *
- *
- * @param multiplier
- * unit to multiply by
- * @return product of two units
- * @throws IllegalArgumentException
- * if {@code multiplier} is not compatible for multiplication as described above
- * @throws NullPointerException
- * if {@code multiplier} is null
- * @since 2018-12-22
- * @since v0.1.0
+ * @return name
+ * @since 2019-10-16
*/
- public BaseUnit times(final BaseUnit multiplier) {
- Objects.requireNonNull(multiplier, "other must not be null");
-
- // check that these units can be multiplied
- if (!this.getSystem().equals(multiplier.getSystem()))
- throw new IllegalArgumentException(
- String.format("Incompatible units for multiplication \"%s\" and \"%s\".", this, multiplier));
-
- // multiply the units
- return new BaseUnit(this.getDimension().times(multiplier.getDimension()), this.getSystem());
+ public final String getName() {
+ return this.name;
}
/**
- * Returns this unit, but to an exponent.
- *
- * @param exponent
- * exponent
- * @return result of exponentiation
- * @since 2019-01-15
- * @since v0.1.0
+ * @return symbol
+ * @since 2019-10-16
*/
- @Override
- public BaseUnit toExponent(final int exponent) {
- return this.getSystem().getBaseUnit(this.getDimension().toExponent(exponent));
+ public final String getSymbol() {
+ return this.symbol;
}
@Override
public String toString() {
- return String.format("%s base unit of%s dimension %s", this.getSystem(), this.isFullBase ? " base" : "",
- this.getDimension());
+ return String.format("%s (%s)", this.getName(), this.getSymbol());
}
}
diff --git a/src/org/unitConverter/unit/DefaultUnitPrefix.java b/src/org/unitConverter/unit/DefaultUnitPrefix.java
deleted file mode 100644
index 4a9e487..0000000
--- a/src/org/unitConverter/unit/DefaultUnitPrefix.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.unit;
-
-import java.util.Objects;
-
-/**
- * The default implementation of {@code UnitPrefix}, which contains a multiplier and nothing else.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public final class DefaultUnitPrefix implements UnitPrefix {
- private final double multiplier;
-
- /**
- * Creates the {@code DefaultUnitPrefix}.
- *
- * @param multiplier
- * @since 2019-01-14
- * @since v0.2.0
- */
- public DefaultUnitPrefix(final double multiplier) {
- this.multiplier = multiplier;
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (!(obj instanceof DefaultUnitPrefix))
- return false;
- final DefaultUnitPrefix other = (DefaultUnitPrefix) obj;
- return Double.doubleToLongBits(this.multiplier) == Double.doubleToLongBits(other.multiplier);
- }
-
- @Override
- public double getMultiplier() {
- return this.multiplier;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(this.multiplier);
- }
-
- @Override
- public String toString() {
- return String.format("Unit prefix equal to %s", this.multiplier);
- }
-}
diff --git a/src/org/unitConverter/unit/FunctionalUnit.java b/src/org/unitConverter/unit/FunctionalUnit.java
index e3db43a..7ddd876 100644
--- a/src/org/unitConverter/unit/FunctionalUnit.java
+++ b/src/org/unitConverter/unit/FunctionalUnit.java
@@ -19,13 +19,15 @@ 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 AbstractUnit {
+final class FunctionalUnit extends Unit {
/**
* Returns a unit from its base and the functions it uses to convert to and from its base.
*
@@ -42,7 +44,7 @@ final class FunctionalUnit extends AbstractUnit {
* @throws NullPointerException
* if any argument is null
*/
- public static FunctionalUnit valueOf(final BaseUnit base, final DoubleUnaryOperator converterFrom,
+ public static FunctionalUnit valueOf(final ObjectProduct base, final DoubleUnaryOperator converterFrom,
final DoubleUnaryOperator converterTo) {
return new FunctionalUnit(base, converterFrom, converterTo);
}
@@ -76,7 +78,7 @@ final class FunctionalUnit extends AbstractUnit {
* if any argument is null
* @since 2019-05-22
*/
- private FunctionalUnit(final BaseUnit base, final DoubleUnaryOperator converterFrom,
+ private FunctionalUnit(final ObjectProduct base, final DoubleUnaryOperator converterFrom,
final DoubleUnaryOperator converterTo) {
super(base);
this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null.");
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
index 1b1ac97..c397250 100644
--- a/src/org/unitConverter/unit/LinearUnit.java
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -18,73 +18,78 @@ package org.unitConverter.unit;
import java.util.Objects;
-import org.unitConverter.dimension.UnitDimension;
import org.unitConverter.math.DecimalComparison;
+import org.unitConverter.math.ObjectProduct;
/**
- * A unit that is equal to a certain number multiplied by its base.
- *
- * {@code LinearUnit} does not have any public constructors or static factories. In order to obtain a {@code LinearUnit}
- * instance, multiply its base by the conversion factor. Example:
- *
- *
- * LinearUnit foot = METRE.times(0.3048);
- *
- *
- * (where {@code METRE} is a {@code BaseUnit} instance)
- *
+ * 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 2018-12-22
- * @since v0.1.0
+ * @since 2019-10-16
*/
-public class LinearUnit extends AbstractUnit {
+public final class LinearUnit extends Unit {
/**
- * The value of one of this unit in this unit's base 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'
*
- * @since 2018-12-22
- * @since v0.1.0
+ * @param unit
+ * unit to convert
+ * @param value
+ * value to convert
+ * @return value expressed as a {@code LinearUnit}
+ * @since 2019-10-16
*/
- private final double conversionFactor;
+ public static LinearUnit fromUnitValue(final Unit unit, final double value) {
+ return new LinearUnit(unit.getBase(), unit.convertToBase(value));
+ }
/**
+ * 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}.
*
- * Creates the {@code LinearUnit}.
- *
- * @param base
- * unit's base
+ * @param unitBase
+ * unit base to multiply by
* @param conversionFactor
- * value of one of this unit in its base
- * @since 2018-12-23
- * @since v0.1.0
+ * number to multiply base by
+ * @return product of base and conversion factor
+ * @since 2019-10-16
*/
- LinearUnit(final BaseUnit base, final double conversionFactor) {
- super(base);
- this.conversionFactor = conversionFactor;
+ public static LinearUnit valueOf(final ObjectProduct unitBase, final double conversionFactor) {
+ return new LinearUnit(unitBase, conversionFactor);
}
/**
- * Creates the {@code LinearUnit} as a base unit.
+ * The value of this unit as represented in its base form. Mathematically,
*
- * @param dimension
- * dimension measured by unit
- * @param system
- * system unit is part of
- * @since 2019-01-25
- * @since v0.1.0
+ *
+ * this = conversionFactor * getBase()
+ *
+ *
+ * @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
*/
- LinearUnit(final UnitDimension dimension, final UnitSystem system, final double conversionFactor) {
- super(dimension, system);
+ private LinearUnit(final ObjectProduct unitBase, final double conversionFactor) {
+ super(unitBase);
this.conversionFactor = conversionFactor;
}
@Override
- public double convertFromBase(final double value) {
+ protected double convertFromBase(final double value) {
return value / this.getConversionFactor();
}
@Override
- public double convertToBase(final double value) {
+ protected double convertToBase(final double value) {
return value * this.getConversionFactor();
}
@@ -98,21 +103,15 @@ public class LinearUnit extends AbstractUnit {
* @since v0.1.0
*/
public LinearUnit dividedBy(final double divisor) {
- return new LinearUnit(this.getBase(), this.getConversionFactor() / divisor);
+ return valueOf(this.getBase(), this.getConversionFactor() / divisor);
}
/**
* Returns the quotient of this unit and another.
- *
- * Two units can be divided if they are part of the same unit system. If {@code divisor} does not meet this
- * condition, an {@code IllegalArgumentException} should be thrown.
- *
*
* @param divisor
* unit to divide by
* @return quotient of two units
- * @throws IllegalArgumentException
- * if {@code divisor} is not compatible for division as described above
* @throws NullPointerException
* if {@code divisor} is null
* @since 2018-12-22
@@ -121,14 +120,9 @@ public class LinearUnit extends AbstractUnit {
public LinearUnit dividedBy(final LinearUnit divisor) {
Objects.requireNonNull(divisor, "other must not be null");
- // check that these units can be multiplied
- if (!this.getSystem().equals(divisor.getSystem()))
- throw new IllegalArgumentException(
- String.format("Incompatible units for division \"%s\" and \"%s\".", this, divisor));
-
// divide the units
- final BaseUnit base = this.getBase().dividedBy(divisor.getBase());
- return new LinearUnit(base, this.getConversionFactor() / divisor.getConversionFactor());
+ final ObjectProduct base = this.getBase().dividedBy(divisor.getBase());
+ return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor());
}
@Override
@@ -136,28 +130,38 @@ public class LinearUnit extends AbstractUnit {
if (!(obj instanceof LinearUnit))
return false;
final LinearUnit other = (LinearUnit) obj;
- return Objects.equals(this.getSystem(), other.getSystem())
- && Objects.equals(this.getDimension(), other.getDimension())
+ return Objects.equals(this.getBase(), other.getBase())
&& DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor());
}
/**
- * @return conversion factor between this unit and its base
- * @since 2018-12-22
- * @since v0.1.0
+ * @return conversion factor
+ * @since 2019-10-16
*/
- public final double getConversionFactor() {
+ public double getConversionFactor() {
return this.conversionFactor;
}
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = result * prime + this.getSystem().hashCode();
- result = result * prime + this.getDimension().hashCode();
- result = result * prime + Double.hashCode(this.getConversionFactor());
- return result;
+ 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;
}
/**
@@ -186,7 +190,7 @@ public class LinearUnit extends AbstractUnit {
String.format("Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahendend));
// add the units
- return new LinearUnit(this.getBase(), this.getConversionFactor() - subtrahendend.getConversionFactor());
+ return valueOf(this.getBase(), this.getConversionFactor() - subtrahendend.getConversionFactor());
}
/**
@@ -215,7 +219,7 @@ public class LinearUnit extends AbstractUnit {
String.format("Incompatible units for addition \"%s\" and \"%s\".", this, addend));
// add the units
- return new LinearUnit(this.getBase(), this.getConversionFactor() + addend.getConversionFactor());
+ return valueOf(this.getBase(), this.getConversionFactor() + addend.getConversionFactor());
}
/**
@@ -228,21 +232,15 @@ public class LinearUnit extends AbstractUnit {
* @since v0.1.0
*/
public LinearUnit times(final double multiplier) {
- return new LinearUnit(this.getBase(), this.getConversionFactor() * multiplier);
+ return valueOf(this.getBase(), this.getConversionFactor() * multiplier);
}
/**
* Returns the product of this unit and another.
- *
- * Two units can be multiplied if they are part of the same unit system. If {@code multiplier} does not meet this
- * condition, an {@code IllegalArgumentException} should be thrown.
- *
*
* @param multiplier
* unit to multiply by
* @return product of two units
- * @throws IllegalArgumentException
- * if {@code multiplier} is not compatible for multiplication as described above
* @throws NullPointerException
* if {@code multiplier} is null
* @since 2018-12-22
@@ -251,32 +249,28 @@ public class LinearUnit extends AbstractUnit {
public LinearUnit times(final LinearUnit multiplier) {
Objects.requireNonNull(multiplier, "other must not be null");
- // check that these units can be multiplied
- if (!this.getSystem().equals(multiplier.getSystem()))
- throw new IllegalArgumentException(
- String.format("Incompatible units for multiplication \"%s\" and \"%s\".", this, multiplier));
-
// multiply the units
- final BaseUnit base = this.getBase().times(multiplier.getBase());
- return new LinearUnit(base, this.getConversionFactor() * multiplier.getConversionFactor());
+ final ObjectProduct base = this.getBase().times(multiplier.getBase());
+ return valueOf(base, this.getConversionFactor() * multiplier.getConversionFactor());
}
/**
* Returns this unit but to an exponent.
*
* @param exponent
- * exponent to exponientate unit to
- * @return exponientated unit
+ * exponent to exponentiate unit to
+ * @return exponentiated unit
* @since 2019-01-15
* @since v0.1.0
*/
public LinearUnit toExponent(final int exponent) {
- return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
+ return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
}
+ // returns a definition of the unit
@Override
public String toString() {
- return super.toString() + String.format(" (equal to %s * base)", this.getConversionFactor());
+ return Double.toString(this.conversionFactor) + " * " + this.getBase().toString(BaseUnit::getSymbol);
}
/**
diff --git a/src/org/unitConverter/unit/NonlinearUnits.java b/src/org/unitConverter/unit/NonlinearUnits.java
deleted file mode 100644
index eda4a74..0000000
--- a/src/org/unitConverter/unit/NonlinearUnits.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.unit;
-
-/**
- * Some major nonlinear units.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public final class NonlinearUnits {
- public static final Unit CELSIUS = Unit.fromConversionFunctions(SI.KELVIN, tempK -> tempK - 273.15,
- tempC -> tempC + 273.15);
-
- public static final Unit FAHRENHEIT = Unit.fromConversionFunctions(SI.KELVIN, tempK -> tempK * 1.8 - 459.67,
- tempF -> (tempF + 459.67) / 1.8);
-
- // You may NOT get a NonlinearUnits instance.
- private NonlinearUnits() {
- throw new AssertionError();
- }
-}
diff --git a/src/org/unitConverter/unit/SI.java b/src/org/unitConverter/unit/SI.java
index 46e6ff1..f623179 100644
--- a/src/org/unitConverter/unit/SI.java
+++ b/src/org/unitConverter/unit/SI.java
@@ -1,74 +1,228 @@
-/**
- * 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 .
- */
-package org.unitConverter.unit;
-
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-import org.unitConverter.dimension.StandardDimensions;
-import org.unitConverter.dimension.UnitDimension;
-
-/**
- * The SI, which holds all SI units
- *
- * @author Adrien Hopkins
- * @since 2018-12-23
- * @since v0.1.0
- */
-public enum SI implements UnitSystem {
- SI;
-
- /**
- * This system's base units.
- *
- * @since 2019-01-25
- * @since v0.1.0
- */
- private static final Set baseUnits = new HashSet<>();
-
- // base units
- public static final BaseUnit METRE = SI.getBaseUnit(StandardDimensions.LENGTH);
- public static final BaseUnit KILOGRAM = SI.getBaseUnit(StandardDimensions.MASS);
- public static final BaseUnit SECOND = SI.getBaseUnit(StandardDimensions.TIME);
- public static final BaseUnit AMPERE = SI.getBaseUnit(StandardDimensions.ELECTRIC_CURRENT);
- public static final BaseUnit KELVIN = SI.getBaseUnit(StandardDimensions.TEMPERATURE);
- public static final BaseUnit MOLE = SI.getBaseUnit(StandardDimensions.QUANTITY);
- public static final BaseUnit CANDELA = SI.getBaseUnit(StandardDimensions.LUMINOUS_INTENSITY);
-
- @Override
- public BaseUnit getBaseUnit(final UnitDimension dimension) {
- // try to find an existing unit before creating a new one
-
- Objects.requireNonNull(dimension, "dimension must not be null.");
- for (final BaseUnit unit : baseUnits) {
- // it will be equal since the conditions for equality are dimension and system,
- // and system is always SI.
- if (unit.getDimension().equals(dimension))
- return unit;
- }
- // could not find an existing base unit
- final BaseUnit unit = new BaseUnit(dimension, this);
- baseUnits.add(unit);
- return unit;
- }
-
- @Override
- public String getName() {
- return "SI";
- }
-}
+/**
+ * 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 .
+ */
+package org.unitConverter.unit;
+
+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.
+ *
+ *
+ * This class does not include prefixed units. To obtain prefixed units, use {@link LinearUnit#withPrefix}:
+ *
+ *
+ * LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
+ *
+ *
+ *
+ * @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", "$");
+
+ // You may NOT get SI.BaseUnits instances!
+ private BaseUnits() {
+ throw new AssertionError();
+ }
+ }
+
+ // dimensions used in the SI, as ObjectProducts
+ public static final class Dimensions {
+ public static final ObjectProduct EMPTY = ObjectProduct.empty();
+ public static final ObjectProduct LENGTH = ObjectProduct.oneOf(BaseDimensions.LENGTH);
+ public static final ObjectProduct MASS = ObjectProduct.oneOf(BaseDimensions.MASS);
+ public static final ObjectProduct TIME = ObjectProduct.oneOf(BaseDimensions.TIME);
+ public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct
+ .oneOf(BaseDimensions.ELECTRIC_CURRENT);
+ public static final ObjectProduct TEMPERATURE = ObjectProduct.oneOf(BaseDimensions.TEMPERATURE);
+ public static final ObjectProduct QUANTITY = ObjectProduct.oneOf(BaseDimensions.QUANTITY);
+ public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct
+ .oneOf(BaseDimensions.LUMINOUS_INTENSITY);
+ public static final ObjectProduct INFORMATION = ObjectProduct.oneOf(BaseDimensions.INFORMATION);
+ public static final ObjectProduct CURRENCY = ObjectProduct.oneOf(BaseDimensions.CURRENCY);
+ // derived dimensions without named SI units
+ public static final ObjectProduct AREA = LENGTH.times(LENGTH);
+
+ public static final ObjectProduct VOLUME = AREA.times(LENGTH);
+ public static final ObjectProduct VELOCITY = LENGTH.dividedBy(TIME);
+ public static final ObjectProduct ACCELERATION = VELOCITY.dividedBy(TIME);
+ public static final ObjectProduct WAVENUMBER = EMPTY.dividedBy(LENGTH);
+ public static final ObjectProduct MASS_DENSITY = MASS.dividedBy(VOLUME);
+ public static final ObjectProduct SURFACE_DENSITY = MASS.dividedBy(AREA);
+ public static final ObjectProduct SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
+ public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
+ public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
+ public static final ObjectProduct CONCENTRATION = QUANTITY.dividedBy(VOLUME);
+ public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION.times(MASS);
+ public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
+ public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
+ public static final ObjectProduct REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
+ public static final ObjectProduct ANGLE = LENGTH.dividedBy(LENGTH);
+ public static final ObjectProduct SOLID_ANGLE = AREA.dividedBy(AREA);
+ // derived dimensions with named SI units
+ public static final ObjectProduct FREQUENCY = EMPTY.dividedBy(TIME);
+
+ public static final ObjectProduct FORCE = MASS.times(ACCELERATION);
+ public static final ObjectProduct ENERGY = FORCE.times(LENGTH);
+ public static final ObjectProduct POWER = ENERGY.dividedBy(TIME);
+ public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
+ public static final ObjectProduct VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
+ public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
+ public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
+ public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE.times(TIME);
+ public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
+ public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
+ public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
+ public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
+ public static final ObjectProduct SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
+ public static final ObjectProduct 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();
+ public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit();
+ public static final LinearUnit SECOND = BaseUnits.SECOND.asLinearUnit();
+ public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit();
+ public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit();
+ public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit();
+ public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit();
+ public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit();
+ public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit();
+
+ // Non-base units
+ public static final LinearUnit RADIAN = METRE.dividedBy(METRE);
+ public static final LinearUnit STERADIAN = RADIAN.times(RADIAN);
+ public static final LinearUnit HERTZ = ONE.dividedBy(SECOND); // for periodic phenomena
+ public static final LinearUnit NEWTON = KILOGRAM.times(METRE).dividedBy(SECOND.times(SECOND));
+ public static final LinearUnit PASCAL = NEWTON.dividedBy(METRE.times(METRE));
+ public static final LinearUnit JOULE = NEWTON.times(METRE);
+ public static final LinearUnit WATT = JOULE.dividedBy(SECOND);
+ public static final LinearUnit COULOMB = AMPERE.times(SECOND);
+ public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB);
+ public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT);
+ public static final LinearUnit OHM = VOLT.dividedBy(AMPERE);
+ public static final LinearUnit SIEMENS = ONE.dividedBy(OHM);
+ public static final LinearUnit WEBER = VOLT.times(SECOND);
+ public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE));
+ public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE);
+ public static final LinearUnit LUMEN = CANDELA.times(STERADIAN);
+ public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE));
+ public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND); // for activity referred to a nucleotide
+ public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM); // for absorbed dose
+ public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM); // for dose equivalent
+ public static final LinearUnit KATAL = MOLE.dividedBy(SECOND);
+
+ // Non-SI units included for convenience
+ public static final Unit CELSIUS = Unit.fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15,
+ tempC -> tempC + 273.15);
+ public static final LinearUnit MINUTE = SECOND.times(60);
+ public static final LinearUnit HOUR = MINUTE.times(60);
+ public static final LinearUnit DAY = HOUR.times(60);
+ public static final LinearUnit DEGREE = RADIAN.times(360 / (2 * Math.PI));
+ public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60);
+ public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60);
+ public static final LinearUnit ASTRONOMICAL_UNIT = METRE.times(149597870700.0);
+ public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT.times(ARCSECOND);
+ public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0);
+ public static final LinearUnit LITRE = METRE.times(METRE).times(METRE).dividedBy(1000.0);
+ public static final LinearUnit TONNE = KILOGRAM.times(1000.0);
+ public static final LinearUnit DALTON = KILOGRAM.times(1.660539040e-27); // approximate value
+ public static final LinearUnit ELECTRONVOLT = JOULE.times(1.602176634e-19);
+ public static final Unit NEPER = Unit.fromConversionFunctions(ONE.getBase(), pr -> 0.5 * Math.log(pr),
+ Np -> Math.exp(2 * Np));
+ public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(), pr -> Math.log10(pr),
+ dB -> Math.pow(10, dB));
+ public static final Unit DECIBEL = Unit.fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr),
+ dB -> Math.pow(10, dB / 10));
+
+ /// The prefixes of the SI
+ // expanding decimal prefixes
+ public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3);
+ public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6);
+ public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9);
+ public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12);
+ public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15);
+ public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18);
+ public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21);
+ public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24);
+
+ // contracting decimal prefixes
+ public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3);
+ public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6);
+ public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9);
+ public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12);
+ public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15);
+ public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18);
+ public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21);
+ public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24);
+
+ // prefixes that don't match the pattern of thousands
+ public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1);
+ public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2);
+ public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1);
+ public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2);
+ public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024);
+ public static final UnitPrefix MEBI = KIBI.times(1024);
+ public static final UnitPrefix GIBI = MEBI.times(1024);
+ public static final UnitPrefix TEBI = GIBI.times(1024);
+ public static final UnitPrefix PEBI = TEBI.times(1024);
+ public static final UnitPrefix EXBI = PEBI.times(1024);
+
+ // You may NOT get SI instances!
+ private SI() {
+ throw new AssertionError();
+ }
+}
diff --git a/src/org/unitConverter/unit/SIPrefix.java b/src/org/unitConverter/unit/SIPrefix.java
deleted file mode 100644
index 31d7ff2..0000000
--- a/src/org/unitConverter/unit/SIPrefix.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.unit;
-
-/**
- * The SI prefixes.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public enum SIPrefix implements UnitPrefix {
- DECA(10), HECTO(100), KILO(1e3), MEGA(1e6), GIGA(1e9), TERA(1e12), PETA(1e15), EXA(1e18), ZETTA(1e21), YOTTA(
- 1e24), DECI(0.1), CENTI(0.01), MILLI(
- 1e-3), MICRO(1e-6), NANO(1e-9), PICO(1e-12), FEMTO(1e-15), ATTO(1e-18), ZEPTO(1e-21), YOCTO(1e-24);
-
- private final double multiplier;
-
- /**
- * Creates the {@code SIPrefix}.
- *
- * @param multiplier
- * prefix's multiplier
- * @since 2019-01-14
- * @since v0.1.0
- */
- private SIPrefix(final double multiplier) {
- this.multiplier = multiplier;
- }
-
- /**
- * @return value
- * @since 2019-01-14
- * @since v0.1.0
- */
- @Override
- public final double getMultiplier() {
- return this.multiplier;
- }
-}
diff --git a/src/org/unitConverter/unit/Unit.java b/src/org/unitConverter/unit/Unit.java
index 54f0ab5..d4eb86e 100644
--- a/src/org/unitConverter/unit/Unit.java
+++ b/src/org/unitConverter/unit/Unit.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 Adrien Hopkins
+ * 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
@@ -16,19 +16,20 @@
*/
package org.unitConverter.unit;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
import java.util.function.DoubleUnaryOperator;
-import org.unitConverter.dimension.UnitDimension;
+import org.unitConverter.math.ObjectProduct;
/**
- * A unit that has an associated base unit, and can convert a value expressed in it to and from that base.
+ * A unit that is composed of base units.
*
* @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
+ * @since 2019-10-16
*/
-public interface Unit {
+public abstract class Unit {
/**
* Returns a unit from its base and the functions it uses to convert to and from its base.
*
@@ -51,11 +52,49 @@ public interface Unit {
* @throws NullPointerException
* if any argument is null
*/
- public static Unit fromConversionFunctions(final BaseUnit base, final DoubleUnaryOperator converterFrom,
- final DoubleUnaryOperator converterTo) {
+ public static Unit fromConversionFunctions(final ObjectProduct base,
+ final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo) {
return FunctionalUnit.valueOf(base, converterFrom, converterTo);
}
+ /**
+ * The combination of units that this unit is based on.
+ *
+ * @since 2019-10-16
+ */
+ private final ObjectProduct unitBase;
+
+ /**
+ * Cache storing the result of getDimension()
+ *
+ * @since 2019-10-16
+ */
+ private transient ObjectProduct dimension = null;
+
+ /**
+ * A constructor that constructs {@code BaseUnit} instances.
+ *
+ * @since 2019-10-16
+ */
+ Unit() {
+ if (this instanceof BaseUnit) {
+ this.unitBase = ObjectProduct.oneOf((BaseUnit) this);
+ } else
+ throw new AssertionError();
+ }
+
+ /**
+ * Creates the {@code AbstractUnit}.
+ *
+ * @param unitBase
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if unitBase is null
+ */
+ protected Unit(final ObjectProduct unitBase) {
+ this.unitBase = Objects.requireNonNull(unitBase, "unitBase must not be null.");
+ }
+
/**
* Checks if a value expressed in this unit can be converted to a value expressed in {@code other}
*
@@ -67,7 +106,7 @@ public interface Unit {
* @throws NullPointerException
* if other is null
*/
- default boolean canConvertTo(final Unit other) {
+ public final boolean canConvertTo(final Unit other) {
Objects.requireNonNull(other, "other must not be null.");
return Objects.equals(this.getBase(), other.getBase());
}
@@ -88,7 +127,7 @@ public interface Unit {
* @since 2018-12-22
* @since v0.1.0
*/
- double convertFromBase(double value);
+ protected abstract double convertFromBase(double value);
/**
* Converts a value expressed in this unit to a value expressed in {@code other}.
@@ -101,11 +140,11 @@ public interface Unit {
* @since 2019-05-22
* @throws IllegalArgumentException
* if {@code other} is incompatible for conversion with this unit (as tested by
- * {@link Unit#canConvertTo}).
+ * {@link IUnit#canConvertTo}).
* @throws NullPointerException
* if other is null
*/
- default double convertTo(final Unit other, final double value) {
+ 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));
@@ -129,36 +168,38 @@ public interface Unit {
* @since 2018-12-22
* @since v0.1.0
*/
- double convertToBase(double value);
+ protected abstract double convertToBase(double value);
/**
- *
- * Returns the base unit associated with this unit.
- *
- *
- * The dimension of this unit must be equal to the dimension of the returned unit.
- *
- *
- * If this unit is a base unit, this method should return this unit.\
- *
- *
- * @return base unit associated with this unit
+ * @return combination of units that this unit is based on
* @since 2018-12-22
* @since v0.1.0
*/
- BaseUnit getBase();
+ public final ObjectProduct getBase() {
+ return this.unitBase;
+ }
/**
* @return dimension measured by this unit
* @since 2018-12-22
* @since v0.1.0
*/
- UnitDimension getDimension();
+ public final ObjectProduct getDimension() {
+ if (this.dimension == null) {
+ final Map mapping = this.unitBase.exponentMap();
+ final Map dimensionMap = new HashMap<>();
- /**
- * @return system that this unit is a part of
- * @since 2018-12-23
- * @since v0.1.0
- */
- UnitSystem getSystem();
+ for (final BaseUnit key : mapping.keySet()) {
+ dimensionMap.put(key.getBaseDimension(), mapping.get(key));
+ }
+
+ this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
+ }
+ return this.dimension;
+ }
+
+ @Override
+ public String toString() {
+ return "Unit derived from base " + this.getBase().toString();
+ }
}
diff --git a/src/org/unitConverter/unit/UnitDatabase.java b/src/org/unitConverter/unit/UnitDatabase.java
new file mode 100644
index 0000000..a2b11c3
--- /dev/null
+++ b/src/org/unitConverter/unit/UnitDatabase.java
@@ -0,0 +1,1653 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.unit;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+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.Iterator;
+import java.util.List;
+import java.util.Map;
+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.DecimalComparison;
+import org.unitConverter.math.ExpressionParser;
+import org.unitConverter.math.ObjectProduct;
+
+/**
+ * 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.
+ *
+ * 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.
+ *
+ *
+ * The rules for applying prefixes onto units are the following:
+ *
+ * - Prefixes can only be applied to linear units.
+ * - 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.
+ * - 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".
+ *
+ *
+ *
+ * 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}.
+ *
+ *
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), {@link #containsValue} and {@link #values()}
+ * currently ignore prefixes.
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitMap implements Map {
+ /**
+ * The class used for entry sets.
+ *
+ *
+ * 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.
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitEntrySet extends AbstractSet> {
+ /**
+ * The entry for this set.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitEntry implements Entry {
+ 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> {
+ // position in the unit list
+ private int unitNamePosition = 0;
+ // the indices of the prefixes attached to the current unit
+ private final List prefixCoordinates = new ArrayList<>();
+
+ // values from the unit entry set
+ private final Map map;
+ private transient final List unitNames;
+ private transient final List 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 next() {
+ // get next element
+ final Entry 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 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 e) {
+ throw new UnsupportedOperationException("Cannot add to an immutable set");
+ }
+
+ @Override
+ public boolean addAll(final Collection extends Map.Entry> 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 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 tempEntry = (Entry) 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> 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> 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[] 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.
+ *
+ *
+ * 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.
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private static final class PrefixedUnitNameSet extends AbstractSet {
+ /**
+ * 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 {
+ // position in the unit list
+ private int unitNamePosition = 0;
+ // the indices of the prefixes attached to the current unit
+ private final List prefixCoordinates = new ArrayList<>();
+
+ // values from the unit name set
+ private final Map map;
+ private transient final List unitNames;
+ private transient final List 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 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[] 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 units;
+
+ /**
+ * The available prefixes for use.
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final Map prefixes;
+
+ // caches
+ private transient Collection values = null;
+ private transient Set keySet = null;
+ private transient Set> 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 units, final Map 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}
+ *
+ *
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method only tests for prefixless units.
+ *
+ */
+ @Override
+ public boolean containsValue(final Object value) {
+ return this.units.containsValue(value);
+ }
+
+ @Override
+ public Set> 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 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}
+ *
+ *
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method ignores prefixes.
+ *
+ */
+ @Override
+ public Collection values() {
+ if (this.values == null) {
+ this.values = Collections.unmodifiableCollection(this.units.values());
+ }
+ return this.values;
+ }
+ }
+
+ /**
+ * 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 units in this system, excluding prefixes.
+ *
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+ private final Map prefixlessUnits;
+
+ /**
+ * The unit prefixes in this system.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private final Map prefixes;
+
+ /**
+ * The dimensions in this system.
+ *
+ * @since 2019-03-14
+ * @since v0.2.0
+ */
+ private final Map> dimensions;
+
+ /**
+ * A map mapping strings to units (including prefixes)
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final Map units;
+
+ /**
+ * A parser that can parse unit expressions.
+ *
+ * @since 2019-03-22
+ * @since v0.2.0
+ */
+ private final ExpressionParser 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 prefix expressions
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ private final ExpressionParser 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> 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.prefixlessUnits = new HashMap<>();
+ this.prefixes = new HashMap<>();
+ this.dimensions = new HashMap<>();
+ this.units = new PrefixedUnitMap(this.prefixlessUnits, this.prefixes);
+ }
+
+ /**
+ * 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 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 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> dimensionMap() {
+ return Collections.unmodifiableMap(this.dimensions);
+ }
+
+ /**
+ * Gets a unit dimension from the database using its name.
+ *
+ *
+ * This method accepts exponents, like "L^3"
+ *
+ *
+ * @param name
+ * dimension's name
+ * @return dimension
+ * @since 2019-03-14
+ * @since v0.2.0
+ */
+ public ObjectProduct getDimension(final String name) {
+ Objects.requireNonNull(name, "name must not be null.");
+ if (name.contains("^")) {
+ final String[] baseAndExponent = name.split("\\^");
+
+ final ObjectProduct 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
+ *
+ * The expression is a series of any of the following:
+ *
+ * - The name of a unit dimension, which multiplies or divides the result based on preceding operators
+ * - The operators '*' and '/', which multiply and divide (note that just putting two unit dimensions next to each
+ * other is equivalent to multiplication)
+ * - The operator '^' which exponentiates. Exponents must be integers.
+ *
+ *
+ * @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 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;
+ modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
+ modifiedExpression = modifiedExpression.replaceAll("/", " / ");
+ modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^");
+
+ // fix broken spaces
+ 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)
+ if (name.contains("(") && name.contains(")")) {
+ // break it into function name and value
+ final List 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 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 a unit prefix from a prefix expression
+ *
+ * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
+ * another prefix
+ *
+ *
+ * @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;
+ modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
+ modifiedExpression = modifiedExpression.replaceAll("/", " / ");
+ modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
+
+ // fix broken spaces
+ modifiedExpression = modifiedExpression.replaceAll(" +", " ");
+
+ return this.prefixExpressionParser.parseExpression(modifiedExpression);
+ }
+
+ /**
+ * 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) {
+ return this.units.get(name);
+ }
+
+ }
+
+ /**
+ * Uses the database's unit data to parse an expression into a unit
+ *
+ * The expression is a series of any of the following:
+ *
+ * - The name of a unit, which multiplies or divides the result based on preceding operators
+ * - The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
+ * other is equivalent to multiplication)
+ * - The operator '^' which exponentiates. Exponents must be integers.
+ * - A number which is multiplied or divided
+ *
+ * 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("-", " - ");
+ modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
+ modifiedExpression = modifiedExpression.replaceAll("/", " / ");
+ modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
+
+ // fix broken spaces
+ modifiedExpression = modifiedExpression.replaceAll(" +", " ");
+
+ // the previous operation breaks negative numbers, fix them!
+ // (i.e. -2 becomes - 2)
+ for (int i = 2; i < modifiedExpression.length(); i++) {
+ if (modifiedExpression.charAt(i) == '-'
+ && 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.
+ *
+ * Each line in the file should consist of a name and an expression (parsed by getDimensionFromExpression) separated
+ * by any number of tab characters.
+ *
+ *
+ * Allowed exceptions:
+ *
+ * - Anything after a '#' character is considered a comment and ignored.
+ * - Blank lines are also ignored
+ * - 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.
+ *
+ *
+ * @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 File file) {
+ Objects.requireNonNull(file, "file must not be null.");
+ try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
+ // while the reader has lines to read, read a line, then parse it, then add it
+ long lineCounter = 0;
+ while (reader.ready()) {
+ this.addDimensionFromLine(reader.readLine(), ++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.
+ *
+ * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
+ * any number of tab characters.
+ *
+ *
+ * Allowed exceptions:
+ *
+ * - Anything after a '#' character is considered a comment and ignored.
+ * - Blank lines are also ignored
+ * - 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.
+ *
+ *
+ * @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 File file) {
+ Objects.requireNonNull(file, "file must not be null.");
+ try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
+ // while the reader has lines to read, read a line, then parse it, then add it
+ long lineCounter = 0;
+ while (reader.ready()) {
+ this.addUnitOrPrefixFromLine(reader.readLine(), ++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 prefixMap() {
+ return Collections.unmodifiableMap(this.prefixes);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Unit Database with %d units and %d unit prefixes", this.prefixlessUnits.size(),
+ this.prefixes.size());
+ }
+
+ /**
+ * Returns a map mapping unit names to units, including units with prefixes.
+ *
+ * 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}.
+ *
+ *
+ * Specifically, the operations that will throw an IllegalStateException if the map is infinite in size are:
+ *
+ * - {@code unitMap.entrySet().toArray()} (either overloading)
+ * - {@code unitMap.keySet().toArray()} (either overloading)
+ *
+ *
+ *
+ * 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.
+ *
+ *
+ * @return a map mapping unit names to units, including prefixed names
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
+ public Map 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 unitMapPrefixless() {
+ return Collections.unmodifiableMap(this.prefixlessUnits);
+ }
+}
diff --git a/src/org/unitConverter/unit/UnitDatabaseTest.java b/src/org/unitConverter/unit/UnitDatabaseTest.java
new file mode 100644
index 0000000..164172b
--- /dev/null
+++ b/src/org/unitConverter/unit/UnitDatabaseTest.java
@@ -0,0 +1,307 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.unit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * A test for the {@link UnitDatabase} class. This is NOT part of this program's public API.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+class UnitDatabaseTest {
+ // some linear units and one nonlinear
+ private static final Unit U = SI.METRE;
+ private static final Unit V = SI.KILOGRAM;
+ private static final Unit W = SI.SECOND;
+
+ // used for testing expressions
+ // J = U^2 * V / W^2
+ private static final LinearUnit J = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
+ private static final LinearUnit K = SI.KELVIN;
+
+ private static final Unit NONLINEAR = Unit.fromConversionFunctions(SI.METRE.getBase(), o -> o + 1, o -> o - 1);
+
+ // make the prefix values prime so I can tell which multiplications were made
+ private static final UnitPrefix A = UnitPrefix.valueOf(2);
+ private static final UnitPrefix B = UnitPrefix.valueOf(3);
+ private static final UnitPrefix C = UnitPrefix.valueOf(5);
+ private static final UnitPrefix AB = UnitPrefix.valueOf(7);
+ private static final UnitPrefix BC = UnitPrefix.valueOf(11);
+
+ /**
+ * Confirms that operations that shouldn't function for infinite databases throw an {@code IllegalStateException}.
+ *
+ * @since 2019-05-03
+ */
+ @Test
+ public void testInfiniteSetExceptions() {
+ // load units
+ final UnitDatabase infiniteDatabase = new UnitDatabase();
+
+ infiniteDatabase.addUnit("J", J);
+ infiniteDatabase.addUnit("K", K);
+
+ infiniteDatabase.addPrefix("A", A);
+ infiniteDatabase.addPrefix("B", B);
+ infiniteDatabase.addPrefix("C", C);
+
+ {
+ boolean exceptionThrown = false;
+ try {
+ infiniteDatabase.unitMap().entrySet().toArray();
+ } catch (final IllegalStateException e) {
+ exceptionThrown = true;
+ // pass!
+ } finally {
+ if (!exceptionThrown) {
+ fail("No IllegalStateException thrown");
+ }
+ }
+ }
+
+ {
+ boolean exceptionThrown = false;
+ try {
+ infiniteDatabase.unitMap().keySet().toArray();
+ } catch (final IllegalStateException e) {
+ exceptionThrown = true;
+ // pass!
+ } finally {
+ if (!exceptionThrown) {
+ fail("No IllegalStateException thrown");
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that prefixes correctly apply to units.
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ @Test
+ public void testPrefixes() {
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ // get the product
+ final Unit abcuNonlinear = database.getUnit("ABCU");
+ assert abcuNonlinear instanceof LinearUnit;
+
+ final LinearUnit abcu = (LinearUnit) abcuNonlinear;
+ assertEquals(A.getMultiplier() * B.getMultiplier() * C.getMultiplier(), abcu.getConversionFactor(), 1e-15);
+ }
+
+ /**
+ * Tests the functionnalites of the prefixless unit map.
+ *
+ *
+ * The map should be an auto-updating view of the units in the database.
+ *
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ @Test
+ public void testPrefixlessUnitMap() {
+ final UnitDatabase database = new UnitDatabase();
+ final Map prefixlessUnits = database.unitMapPrefixless();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+
+ // this should work because the map should be an auto-updating view
+ assertTrue(prefixlessUnits.containsKey("U"));
+ assertFalse(prefixlessUnits.containsKey("Z"));
+
+ assertTrue(prefixlessUnits.containsValue(U));
+ assertFalse(prefixlessUnits.containsValue(NONLINEAR));
+ }
+
+ /**
+ * Tests that the database correctly stores and retrieves units, ignoring prefixes.
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ @Test
+ public void testPrefixlessUnits() {
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+
+ assertTrue(database.containsUnitName("U"));
+ assertFalse(database.containsUnitName("Z"));
+
+ assertEquals(U, database.getUnit("U"));
+ assertEquals(null, database.getUnit("Z"));
+ }
+
+ /**
+ * Test that unit expressions return the correct value.
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ @Test
+ public void testUnitExpressions() {
+ // load units
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+ database.addUnit("fj", J.times(5));
+ database.addUnit("ej", J.times(8));
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ // first test - test prefixes and operations
+ final Unit expected1 = J.withPrefix(A).withPrefix(B).withPrefix(C).withPrefix(C);
+ final Unit actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W");
+
+ assertEquals(expected1, actual1);
+
+ // second test - test addition and subtraction
+ final Unit expected2 = J.times(58);
+ final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej");
+
+ assertEquals(expected2, actual2);
+ }
+
+ /**
+ * Tests both the unit name iterator and the name-unit entry iterator
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ @Test
+ public void testUnitIterator() {
+ // load units
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("J", J);
+ database.addUnit("K", K);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ final int NUM_UNITS = database.unitMapPrefixless().size();
+ final int NUM_PREFIXES = database.prefixMap().size();
+
+ final Iterator nameIterator = database.unitMap().keySet().iterator();
+ final Iterator> entryIterator = database.unitMap().entrySet().iterator();
+
+ int expectedLength = 1;
+ int unitsWithThisLengthSoFar = 0;
+
+ // loop 1000 times
+ for (int i = 0; i < 1000; i++) {
+ // expected length of next
+ if (unitsWithThisLengthSoFar >= NUM_UNITS * (int) Math.pow(NUM_PREFIXES, expectedLength - 1)) {
+ expectedLength++;
+ unitsWithThisLengthSoFar = 0;
+ }
+
+ // test that stuff is valid
+ final String nextName = nameIterator.next();
+ final Unit nextUnit = database.getUnit(nextName);
+ final Entry nextEntry = entryIterator.next();
+
+ assertEquals(expectedLength, nextName.length());
+ assertEquals(nextName, nextEntry.getKey());
+ assertEquals(nextUnit, nextEntry.getValue());
+
+ unitsWithThisLengthSoFar++;
+ }
+
+ // test toString for consistency
+ final String entryIteratorString = entryIterator.toString();
+ for (int i = 0; i < 3; i++) {
+ assertEquals(entryIteratorString, entryIterator.toString());
+ }
+
+ final String nameIteratorString = nameIterator.toString();
+ for (int i = 0; i < 3; i++) {
+ assertEquals(nameIteratorString, nameIterator.toString());
+ }
+ }
+
+ /**
+ * Determine, given a unit name that could mean multiple things, which meaning is chosen.
+ *
+ * For example, "ABCU" could mean "A-B-C-U", "AB-C-U", or "A-BC-U". In this case, "AB-C-U" is the correct choice.
+ *
+ *
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ @Test
+ public void testUnitPrefixCombinations() {
+ // load units
+ final UnitDatabase database = new UnitDatabase();
+
+ database.addUnit("J", J);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+ database.addPrefix("AB", AB);
+ database.addPrefix("BC", BC);
+
+ // test 1 - AB-C-J vs A-BC-J vs A-B-C-J
+ final Unit expected1 = J.withPrefix(AB).withPrefix(C);
+ final Unit actual1 = database.getUnit("ABCJ");
+
+ assertEquals(expected1, actual1);
+
+ // test 2 - ABC-J vs AB-CJ vs AB-C-J
+ database.addUnit("CJ", J.times(13));
+ database.addPrefix("ABC", UnitPrefix.valueOf(17));
+
+ final Unit expected2 = J.times(17);
+ final Unit actual2 = database.getUnit("ABCJ");
+
+ assertEquals(expected2, actual2);
+ }
+}
diff --git a/src/org/unitConverter/unit/UnitPrefix.java b/src/org/unitConverter/unit/UnitPrefix.java
index 9f9645d..514fa1c 100644
--- a/src/org/unitConverter/unit/UnitPrefix.java
+++ b/src/org/unitConverter/unit/UnitPrefix.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 Adrien Hopkins
+ * 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
@@ -16,14 +16,57 @@
*/
package org.unitConverter.unit;
+import org.unitConverter.math.DecimalComparison;
+
/**
- * A prefix that can be attached onto the front of any unit, which multiplies it by a certain value
+ * A prefix that can be applied to a {@code LinearUnit} to multiply it by some value
*
* @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
+ * @since 2019-10-16
*/
-public interface UnitPrefix {
+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);
+ }
+
+ /**
+ * 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) {
+ this.multiplier = multiplier;
+ }
+
+ /**
+ * 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}.
*
@@ -33,16 +76,42 @@ public interface UnitPrefix {
* @since 2019-04-13
* @since v0.2.0
*/
- default UnitPrefix dividedBy(final UnitPrefix other) {
- return new DefaultUnitPrefix(this.getMultiplier() / other.getMultiplier());
+ public UnitPrefix dividedBy(final UnitPrefix other) {
+ return valueOf(this.getMultiplier() / other.getMultiplier());
+ }
+
+ @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());
+ }
+
+ public double getMultiplier() {
+ return this.multiplier;
+ }
+
+ @Override
+ public int hashCode() {
+ return DecimalComparison.hash(this.getMultiplier());
}
/**
- * @return this prefix's multiplier
- * @since 2019-01-14
- * @since v0.1.0
+ * Multiplies this prefix by a scalar
+ *
+ * @param multiplicand
+ * number to multiply by
+ * @return product of prefix and scalar
+ * @since 2019-10-16
*/
- double getMultiplier();
+ public UnitPrefix times(final double multiplicand) {
+ return valueOf(this.getMultiplier() * multiplicand);
+ }
/**
* Multiplies this prefix by {@code other}.
@@ -53,8 +122,8 @@ public interface UnitPrefix {
* @since 2019-04-13
* @since v0.2.0
*/
- default UnitPrefix times(final UnitPrefix other) {
- return new DefaultUnitPrefix(this.getMultiplier() * other.getMultiplier());
+ public UnitPrefix times(final UnitPrefix other) {
+ return valueOf(this.getMultiplier() * other.getMultiplier());
}
/**
@@ -66,7 +135,12 @@ public interface UnitPrefix {
* @since 2019-04-13
* @since v0.2.0
*/
- default UnitPrefix toExponent(final double exponent) {
- return new DefaultUnitPrefix(Math.pow(getMultiplier(), exponent));
+ public UnitPrefix toExponent(final double exponent) {
+ return valueOf(Math.pow(this.getMultiplier(), exponent));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Unit prefix equal to %s", this.multiplier);
}
}
diff --git a/src/org/unitConverter/unit/UnitSystem.java b/src/org/unitConverter/unit/UnitSystem.java
deleted file mode 100644
index 550eff6..0000000
--- a/src/org/unitConverter/unit/UnitSystem.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * 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 .
- */
-package org.unitConverter.unit;
-
-import java.util.Objects;
-
-import org.unitConverter.dimension.UnitDimension;
-
-/**
- * A system of units. Each unit should be aware of its system.
- *
- * @author Adrien Hopkins
- * @since 2018-12-23
- * @since v0.1.0
- */
-public interface UnitSystem {
- /**
- * Gets a base unit for this system and the provided dimension.
- *
- * @param dimension
- * dimension used by base unit
- * @return base unit
- * @throws NullPointerException
- * if dimension is null
- * @since 2019-01-25
- * @since v0.1.0
- */
- default BaseUnit getBaseUnit(final UnitDimension dimension) {
- Objects.requireNonNull(dimension, "dimension must not be null.");
- return new BaseUnit(dimension, this);
- }
-
- /**
- * @return name of system
- * @since 2018-12-23
- * @since v0.1.0
- */
- String getName();
-}
diff --git a/src/org/unitConverter/unit/UnitTest.java b/src/org/unitConverter/unit/UnitTest.java
index 7ae5fbf..c078cfc 100644
--- a/src/org/unitConverter/unit/UnitTest.java
+++ b/src/org/unitConverter/unit/UnitTest.java
@@ -22,7 +22,6 @@ import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import org.junit.jupiter.api.Test;
-import org.unitConverter.dimension.StandardDimensions;
import org.unitConverter.math.DecimalComparison;
/**
@@ -45,17 +44,9 @@ class UnitTest {
assertEquals(foot.minus(inch), SI.METRE.times(0.2794));
}
- @Test
- public void testBaseUnitExclusives() {
- // this test should have a compile error if I am doing something wrong
- final BaseUnit metrePerSecondSquared = SI.METRE.dividedBy(SI.SECOND.toExponent(2));
-
- assertEquals(metrePerSecondSquared, SI.SI.getBaseUnit(StandardDimensions.ACCELERATION));
- }
-
@Test
public void testConversion() {
- final BaseUnit metre = SI.METRE;
+ final LinearUnit metre = SI.METRE;
final Unit inch = metre.times(0.0254);
assertEquals(1.9, inch.convertTo(metre, 75), 0.01);
@@ -77,8 +68,8 @@ class UnitTest {
@Test
public void testEquals() {
- final BaseUnit metre = SI.METRE;
- final Unit meter = SI.SI.getBaseUnit(StandardDimensions.LENGTH);
+ final LinearUnit metre = SI.METRE;
+ final Unit meter = SI.BaseUnits.METRE.asLinearUnit();
assertEquals(metre, meter);
}
@@ -87,7 +78,7 @@ class UnitTest {
public void testMultiplicationAndDivision() {
// test unit-times-unit multiplication
final LinearUnit generatedJoule = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
- final LinearUnit actualJoule = SI.SI.getBaseUnit(StandardDimensions.ENERGY);
+ final LinearUnit actualJoule = SI.JOULE;
assertEquals(generatedJoule, actualJoule);
@@ -96,14 +87,14 @@ class UnitTest {
final LinearUnit hour = SI.SECOND.times(3600);
final LinearUnit generatedKPH = kilometre.dividedBy(hour);
- final LinearUnit actualKPH = SI.SI.getBaseUnit(StandardDimensions.VELOCITY).dividedBy(3.6);
+ final LinearUnit actualKPH = SI.METRE.dividedBy(SI.SECOND).dividedBy(3.6);
assertEquals(generatedKPH, actualKPH);
}
@Test
public void testPrefixes() {
- final LinearUnit generatedKilometre = SI.METRE.withPrefix(SIPrefix.KILO);
+ final LinearUnit generatedKilometre = SI.METRE.withPrefix(SI.KILO);
final LinearUnit actualKilometre = SI.METRE.times(1000);
assertEquals(generatedKilometre, actualKilometre);
diff --git a/src/org/unitConverter/unit/package-info.java b/src/org/unitConverter/unit/package-info.java
index dd5a939..2d83e1f 100644
--- a/src/org/unitConverter/unit/package-info.java
+++ b/src/org/unitConverter/unit/package-info.java
@@ -15,10 +15,9 @@
* along with this program. If not, see .
*/
/**
- * All of the classes that correspond to the units being converted.
+ * The new definition for units.
*
* @author Adrien Hopkins
- * @since 2019-01-25
- * @since v0.1.0
+ * @since 2019-10-16
*/
package org.unitConverter.unit;
\ No newline at end of file
diff --git a/unitsfile.txt b/unitsfile.txt
index bda9b81..73a29d1 100644
--- a/unitsfile.txt
+++ b/unitsfile.txt
@@ -146,9 +146,9 @@ deg degree
# Use tempC(100) for 100 degrees Celsius
tempCelsius !
-tempFahrenheit !
+#tempFahrenheit !
tempC tempCelsius
-tempF tempFahrenheit
+#tempF tempFahrenheit
# Common time units
minute 60 second
--
cgit v1.2.3
From b491a1d67b513f6a6f549fe6fcb374d731e0beca Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Thu, 17 Oct 2019 16:31:38 -0400
Subject: Changed the ZeroIsNullMap to a more general ConditionalExistenceMap.
---
.../math/ConditionalExistenceCollections.java | 322 +++++++++++++++++++++
.../math/ConditionalExistenceCollectionsTest.java | 159 ++++++++++
src/org/unitConverter/math/ObjectProduct.java | 3 +-
src/org/unitConverter/math/ZeroIsNullMap.java | 129 ---------
src/org/unitConverter/math/ZeroIsNullMapTest.java | 113 --------
5 files changed, 483 insertions(+), 243 deletions(-)
create mode 100644 src/org/unitConverter/math/ConditionalExistenceCollections.java
create mode 100644 src/org/unitConverter/math/ConditionalExistenceCollectionsTest.java
delete mode 100644 src/org/unitConverter/math/ZeroIsNullMap.java
delete mode 100644 src/org/unitConverter/math/ZeroIsNullMapTest.java
diff --git a/src/org/unitConverter/math/ConditionalExistenceCollections.java b/src/org/unitConverter/math/ConditionalExistenceCollections.java
new file mode 100644
index 0000000..c0a69f0
--- /dev/null
+++ b/src/org/unitConverter/math/ConditionalExistenceCollections.java
@@ -0,0 +1,322 @@
+/**
+ * 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 .
+ */
+package org.unitConverter.math;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Elements in these wrapper collections only exist if they pass a condition.
+ *
+ * All of the collections in this class are "views" of the provided collections. They are mutable if the provided
+ * collections are mutable, they allow null if the provided collections allow null, they will reflect changes in the
+ * provided collection, etc.
+ *
+ * The modification operations will always run the corresponding operations, even if the conditional existence
+ * collection doesn't change. For example, if you have a set that ignores even numbers, add(2) will still add a 2 to the
+ * backing set (but the conditional existence set will say it doesn't exist).
+ *
+ * The returned collections do not pass the hashCode and equals operations through to the backing collections,
+ * but rely on {@code Object}'s {@code equals} and {@code hashCode} methods. This is necessary to preserve the contracts
+ * of these operations in the case that the backing collections are sets or lists.
+ *
+ * Other than that, the only difference between the provided collections and the returned collections are that
+ * elements don't exist if they don't pass the provided condition.
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-17
+ */
+// TODO add conditional existence Collections, Lists and Sorted/Navigable Sets/Maps
+public final class ConditionalExistenceCollections {
+ /**
+ * Elements in this wrapper iterator only exist if they pass a condition.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-17
+ * @param
+ * type of elements in iterator
+ */
+ static final class ConditionalExistenceIterator implements Iterator {
+ final Iterator iterator;
+ final Predicate existenceCondition;
+ E nextElement;
+ boolean hasNext;
+
+ /**
+ * Creates the {@code ConditionalExistenceIterator}.
+ *
+ * @param iterator
+ * @param condition
+ * @since 2019-10-17
+ */
+ private ConditionalExistenceIterator(final Iterator iterator, final Predicate condition) {
+ this.iterator = iterator;
+ this.existenceCondition = condition;
+ this.getAndSetNextElement();
+ }
+
+ /**
+ * Gets the next element, and sets nextElement and hasNext accordingly.
+ *
+ * @since 2019-10-17
+ */
+ private void getAndSetNextElement() {
+ do {
+ if (!this.iterator.hasNext()) {
+ this.nextElement = null;
+ this.hasNext = false;
+ return;
+ }
+ this.nextElement = this.iterator.next();
+ } while (!this.existenceCondition.test(this.nextElement));
+ this.hasNext = true;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return this.hasNext;
+ }
+
+ @Override
+ public E next() {
+ if (this.hasNext()) {
+ final E next = this.nextElement;
+ this.getAndSetNextElement();
+ return next;
+ } else
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ this.iterator.remove();
+ }
+ }
+
+ /**
+ * Mappings in this map only exist if the entry passes some condition.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-17
+ * @param
+ * key type
+ * @param
+ * value type
+ */
+ static final class ConditionalExistenceMap extends AbstractMap {
+ Map