From 987fd8406d65505aedecd17e51216eb0ce393fbb Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
+ * 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);}
+ *
--
cgit v1.2.3
From 01b072b98fdd19a2d57afc15a4ee4a80d0bfc0cd Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
- * 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:
- *
- * 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.
- *
- * 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.
- *
- * 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.
- *
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method only tests for prefixless units.
- *
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method ignores prefixes.
- *
- * This method accepts exponents, like "L^3"
- *
- * The expression is a series of any of the following:
- *
- * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
- * another prefix
- *
- * The expression is a series of any of the following:
- *
- * 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:
- *
- * 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:
- *
- * 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:
- *
- *
- *
- *
- *
- * @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
- *
- * 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.
- *
- *
- *
- * @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.
- *
- *
- *
- * @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
- *
- *
- * 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- * 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- * 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- * 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
- * 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- * 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
- * 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- * 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- * 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- * {@code BaseUnit} does not have any public constructors or static factories. There are two ways to obtain - * {@code BaseUnit} instances. - *
- * BaseUnit JOULE = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2)); - *- * - *
- * BaseUnit JOULE = SI.SI.getBaseUnit(StandardDimensions.ENERGY); - *- * - *
- * 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- * {@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
+ * 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
- * 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- * 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+ * 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
- * 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+ * 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: + *
+ * 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+ * 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+ * 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+ * 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+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method ignores prefixes. + *
+ */ + @Override + public Collection+ * This method accepts exponents, like "L^3" + *
+ * + * @param name + * dimension's name + * @return dimension + * @since 2019-03-14 + * @since v0.2.0 + */ + public ObjectProduct+ * The expression is a series of any of the following: + *
+ * 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: + *
+ * 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: + *
+ * 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: + *
+ * 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: + *
+ * 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+ * 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+ * 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- * 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. + * Two units can be subtracted if they have the same base. Note that {@link #canConvertTo} can be used to determine + * this. If {@code subtrahend} does not meet this condition, an {@code IllegalArgumentException} will be thrown. *
* * @param subtrahend @@ -196,8 +196,8 @@ public final class LinearUnit extends Unit { /** * 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. + * Two units can be added if they have the same base. Note that {@link #canConvertTo} can be used to determine this. + * If {@code addend} does not meet this condition, an {@code IllegalArgumentException} will be thrown. *
* * @param addend diff --git a/src/org/unitConverter/unit/Unit.java b/src/org/unitConverter/unit/Unit.java index d4eb86e..7971a41 100644 --- a/src/org/unitConverter/unit/Unit.java +++ b/src/org/unitConverter/unit/Unit.java @@ -52,7 +52,7 @@ public abstract class Unit { * @throws NullPointerException * if any argument is null */ - public static Unit fromConversionFunctions(final ObjectProduct
+ * Note that BaseUnits must have names and symbols. This is because they are used for toString code. Therefore,
+ * the Optionals provided by {@link #getPrimaryName} and {@link #getSymbol} will always contain a value.
*
* @author Adrien Hopkins
* @since 2019-10-16
@@ -38,19 +43,34 @@ public final class BaseUnit extends Unit {
* @since 2019-10-16
*/
public static BaseUnit valueOf(final BaseDimension dimension, final String name, final String symbol) {
- return new BaseUnit(dimension, name, symbol);
+ return new BaseUnit(dimension, name, symbol, new HashSet<>());
+ }
+
+ /**
+ * Gets a base unit from the dimension it measures, its name and its symbol.
+ *
+ * @param dimension
+ * dimension measured by this unit
+ * @param name
+ * name of unit
+ * @param symbol
+ * symbol of unit
+ * @return base unit
+ * @since 2019-10-21
+ */
+ public static BaseUnit valueOf(final BaseDimension dimension, final String name, final String symbol,
+ final Set
+ * 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);}
+ *
+ * {@inheritDoc}
+ */
@Override
public String toString() {
return this.toString(Object::toString);
diff --git a/src/org/unitConverter/unit/BaseDimension.java b/src/org/unitConverter/unit/BaseDimension.java
index 35acd18..8e63a17 100644
--- a/src/org/unitConverter/unit/BaseDimension.java
+++ b/src/org/unitConverter/unit/BaseDimension.java
@@ -39,7 +39,13 @@ public final class BaseDimension {
return new BaseDimension(name, symbol);
}
+ /**
+ * The name of the dimension.
+ */
private final String name;
+ /**
+ * The symbol used by the dimension. Symbols should be short, generally one or two characters.
+ */
private final String symbol;
/**
diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
index e9ef3fa..d9f7965 100644
--- a/src/org/unitConverter/unit/BaseUnit.java
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -63,6 +63,9 @@ public final class BaseUnit extends Unit {
return new BaseUnit(dimension, name, symbol, otherNames);
}
+ /**
+ * The dimension measured by this base unit.
+ */
private final BaseDimension dimension;
/**
diff --git a/src/org/unitConverter/unit/FunctionalUnit.java b/src/org/unitConverter/unit/FunctionalUnit.java
index e2ab6e7..586e0d7 100644
--- a/src/org/unitConverter/unit/FunctionalUnit.java
+++ b/src/org/unitConverter/unit/FunctionalUnit.java
@@ -86,11 +86,21 @@ final class FunctionalUnit extends Unit {
this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null.");
}
+ /**
+ * {@inheritDoc}
+ *
+ * Uses {@code converterFrom} to convert.
+ */
@Override
public double convertFromBase(final double value) {
return this.converterFrom.applyAsDouble(value);
}
+ /**
+ * {@inheritDoc}
+ *
+ * Uses {@code converterTo} to convert.
+ */
@Override
public double convertToBase(final double value) {
return this.converterTo.applyAsDouble(value);
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
index 7b7338b..1532fc4 100644
--- a/src/org/unitConverter/unit/LinearUnit.java
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -128,11 +128,21 @@ public final class LinearUnit extends Unit {
this.conversionFactor = conversionFactor;
}
+ /**
+ * {@inheritDoc}
+ *
+ * Converts by dividing by {@code conversionFactor}
+ */
@Override
protected double convertFromBase(final double value) {
return value / this.getConversionFactor();
}
+ /**
+ * {@inheritDoc}
+ *
+ * Converts by multiplying by {@code conversionFactor}
+ */
@Override
protected double convertToBase(final double value) {
return value * this.getConversionFactor();
@@ -170,6 +180,11 @@ public final class LinearUnit extends Unit {
return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor());
}
+ /**
+ * {@inheritDoc}
+ *
+ * Uses the base and conversion factor of units to test for equality.
+ */
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof LinearUnit))
@@ -187,6 +202,11 @@ public final class LinearUnit extends Unit {
return this.conversionFactor;
}
+ /**
+ * {@inheritDoc}
+ *
+ * Uses the base and conversion factor to compute a hash code.
+ */
@Override
public int hashCode() {
return 31 * this.getBase().hashCode() + DecimalComparison.hash(this.getConversionFactor());
@@ -234,7 +254,7 @@ public final class LinearUnit extends Unit {
throw new IllegalArgumentException(
String.format("Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahendend));
- // add the units
+ // subtract the units
return valueOf(this.getBase(), this.getConversionFactor() - subtrahendend.getConversionFactor());
}
@@ -312,7 +332,10 @@ public final class LinearUnit extends Unit {
return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
}
- // returns a definition of the unit
+ /**
+ * @return a string providing a definition of this unit
+ * @since 2019-10-21
+ */
@Override
public String toString() {
return this.getPrimaryName().orElse("Unnamed unit")
diff --git a/src/org/unitConverter/unit/SI.java b/src/org/unitConverter/unit/SI.java
index 45a81e2..19d63e6 100644
--- a/src/org/unitConverter/unit/SI.java
+++ b/src/org/unitConverter/unit/SI.java
@@ -17,6 +17,7 @@
package org.unitConverter.unit;
import org.unitConverter.math.ObjectProduct;
+import org.unitConverter.unit.Unit.NameSymbol;
/**
* All of the units, prefixes and dimensions that are used by the SI, as well as some outside the SI.
@@ -115,9 +116,9 @@ public final class SI {
public static final ObjectProduct
+ * If this unit and the provided prefix have a primary name, the returned unit will have a primary name (prefix's
+ * name + unit's name).
+ * If this unit and the provided prefix have a symbol, the returned unit will have a symbol.
+ * This method ignores alternate names of both this unit and the provided prefix.
*
* @param prefix
* prefix to apply
* @return unit with prefix
* @since 2019-03-18
* @since v0.2.0
+ * @throws NullPointerException
+ * if prefix is null
*/
public LinearUnit withPrefix(final UnitPrefix prefix) {
- return this.times(prefix.getMultiplier());
+ final LinearUnit unit = this.times(prefix.getMultiplier());
+
+ // create new name and symbol, if possible
+ final String name;
+ if (this.getPrimaryName().isPresent() && prefix.getPrimaryName().isPresent()) {
+ name = prefix.getPrimaryName().get() + this.getPrimaryName().get();
+ } else {
+ name = null;
+ }
+
+ final String symbol;
+ if (this.getSymbol().isPresent() && prefix.getSymbol().isPresent()) {
+ symbol = prefix.getSymbol().get() + this.getSymbol().get();
+ } else {
+ symbol = null;
+ }
+
+ return unit.withName(NameSymbol.ofNullable(name, symbol));
}
}
diff --git a/src/org/unitConverter/unit/NameSymbol.java b/src/org/unitConverter/unit/NameSymbol.java
new file mode 100644
index 0000000..96fab45
--- /dev/null
+++ b/src/org/unitConverter/unit/NameSymbol.java
@@ -0,0 +1,277 @@
+/**
+ * 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