From 77c7c962a6ed810b12685aa9ace4bd8a62761cea 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.
+ * 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 + * 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. + * Because of ambiguities between prefixes (i.e. kilokilo = mega), + * {@link #containsValue} and {@link #values()} currently ignore prefixes. *
* * @author Adrien Hopkins @@ -89,16 +94,19 @@ public final class UnitDatabase { * 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. + * 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. + * 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. + * 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. + * Because of ambiguities between prefixes (i.e. kilokilo = mega), this + * method ignores prefixes. *
*/ @Override public Collection* 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 + * 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 + * @param expression expression to input * @return prefix - * @throws IllegalArgumentException - * if expression cannot be parsed - * @throws NullPointerException - * if any argument is null + * @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(" +", " "); - + // format expression - for (final Entry* 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. + * 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. + * 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}. + * 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: + * 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. + * 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 @@ -1676,9 +1890,10 @@ public final class UnitDatabase { * @since v0.2.0 */ public Map- * This class does not include prefixed units. To obtain prefixed units, use {@link LinearUnit#withPrefix}: + * This class does not include prefixed units. To obtain prefixed units, use + * {@link LinearUnit#withPrefix}: * *
* LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO); @@ -36,42 +42,61 @@ 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 - + 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) + // suppressing warnings since these are the same object, but in a different + /// form (class) @SuppressWarnings("hiding") public static final class BaseUnits { - public static final BaseUnit METRE = BaseUnit.valueOf(BaseDimensions.LENGTH, "metre", "m"); - public static final BaseUnit KILOGRAM = BaseUnit.valueOf(BaseDimensions.MASS, "kilogram", "kg"); - public static final BaseUnit SECOND = BaseUnit.valueOf(BaseDimensions.TIME, "second", "s"); - public static final BaseUnit AMPERE = BaseUnit.valueOf(BaseDimensions.ELECTRIC_CURRENT, "ampere", "A"); - public static final BaseUnit KELVIN = BaseUnit.valueOf(BaseDimensions.TEMPERATURE, "kelvin", "K"); - public static final BaseUnit MOLE = BaseUnit.valueOf(BaseDimensions.QUANTITY, "mole", "mol"); - public static final BaseUnit CANDELA = BaseUnit.valueOf(BaseDimensions.LUMINOUS_INTENSITY, "candela", "cd"); - public static final BaseUnit BIT = BaseUnit.valueOf(BaseDimensions.INFORMATION, "bit", "b"); - public static final BaseUnit DOLLAR = BaseUnit.valueOf(BaseDimensions.CURRENCY, "dollar", "$"); - + public static final 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(); } } - + /** * Constants that relate to the SI or other systems. * @@ -79,189 +104,320 @@ public final class SI { * @since 2019-11-08 */ public static final class Constants { - public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND).dividedBy(SECOND).times(9.80665); + public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND) + .dividedBy(SECOND).times(9.80665); } - + // dimensions used in the SI, as ObjectProducts public static final class Dimensions { - public static final ObjectProductEMPTY = 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 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 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); + 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); - + 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); - + 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 ONE = LinearUnit + .valueOf(ObjectProduct.empty(), 1); public static final LinearUnit METRE = BaseUnits.METRE.asLinearUnit() .withName(NameSymbol.of("metre", "m", "meter")); public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit() .withName(NameSymbol.of("kilogram", "kg")); public static final LinearUnit SECOND = BaseUnits.SECOND.asLinearUnit() .withName(NameSymbol.of("second", "s", "sec")); - public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit().withName(NameSymbol.of("ampere", "A")); - public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit().withName(NameSymbol.of("kelvin", "K")); - public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit().withName(NameSymbol.of("mole", "mol")); - public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit().withName(NameSymbol.of("candela", "cd")); - public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit().withName(NameSymbol.of("bit", "b")); - public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit().withName(NameSymbol.of("dollar", "$")); - + public static final LinearUnit AMPERE = BaseUnits.AMPERE.asLinearUnit() + .withName(NameSymbol.of("ampere", "A")); + public static final LinearUnit KELVIN = BaseUnits.KELVIN.asLinearUnit() + .withName(NameSymbol.of("kelvin", "K")); + public static final LinearUnit MOLE = BaseUnits.MOLE.asLinearUnit() + .withName(NameSymbol.of("mole", "mol")); + public static final LinearUnit CANDELA = BaseUnits.CANDELA.asLinearUnit() + .withName(NameSymbol.of("candela", "cd")); + public static final LinearUnit BIT = BaseUnits.BIT.asLinearUnit() + .withName(NameSymbol.of("bit", "b")); + public static final LinearUnit DOLLAR = BaseUnits.DOLLAR.asLinearUnit() + .withName(NameSymbol.of("dollar", "$")); + // Non-base units - public static final LinearUnit RADIAN = METRE.dividedBy(METRE).withName(NameSymbol.of("radian", "rad")); - public static final LinearUnit STERADIAN = RADIAN.times(RADIAN).withName(NameSymbol.of("steradian", "sr")); - public static final LinearUnit HERTZ = ONE.dividedBy(SECOND).withName(NameSymbol.of("hertz", "Hz")); + public static final LinearUnit RADIAN = METRE.dividedBy(METRE) + .withName(NameSymbol.of("radian", "rad")); + public static final LinearUnit STERADIAN = RADIAN.times(RADIAN) + .withName(NameSymbol.of("steradian", "sr")); + public static final LinearUnit HERTZ = ONE.dividedBy(SECOND) + .withName(NameSymbol.of("hertz", "Hz")); // for periodic phenomena - public static final LinearUnit NEWTON = KILOGRAM.times(METRE).dividedBy(SECOND.times(SECOND)) + public static final LinearUnit NEWTON = KILOGRAM.times(METRE) + .dividedBy(SECOND.times(SECOND)) .withName(NameSymbol.of("newton", "N")); public static final LinearUnit PASCAL = NEWTON.dividedBy(METRE.times(METRE)) .withName(NameSymbol.of("pascal", "Pa")); - public static final LinearUnit JOULE = NEWTON.times(METRE).withName(NameSymbol.of("joule", "J")); - public static final LinearUnit WATT = JOULE.dividedBy(SECOND).withName(NameSymbol.of("watt", "W")); - public static final LinearUnit COULOMB = AMPERE.times(SECOND).withName(NameSymbol.of("coulomb", "C")); - public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB).withName(NameSymbol.of("volt", "V")); - public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT).withName(NameSymbol.of("farad", "F")); - public static final LinearUnit OHM = VOLT.dividedBy(AMPERE).withName(NameSymbol.of("ohm", "\u03A9")); // omega - public static final LinearUnit SIEMENS = ONE.dividedBy(OHM).withName(NameSymbol.of("siemens", "S")); - public static final LinearUnit WEBER = VOLT.times(SECOND).withName(NameSymbol.of("weber", "Wb")); - public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE)).withName(NameSymbol.of("tesla", "T")); - public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE).withName(NameSymbol.of("henry", "H")); - public static final LinearUnit LUMEN = CANDELA.times(STERADIAN).withName(NameSymbol.of("lumen", "lm")); - public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE)).withName(NameSymbol.of("lux", "lx")); - public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND).withName(NameSymbol.of("bequerel", "Bq")); + public static final LinearUnit JOULE = NEWTON.times(METRE) + .withName(NameSymbol.of("joule", "J")); + public static final LinearUnit WATT = JOULE.dividedBy(SECOND) + .withName(NameSymbol.of("watt", "W")); + public static final LinearUnit COULOMB = AMPERE.times(SECOND) + .withName(NameSymbol.of("coulomb", "C")); + public static final LinearUnit VOLT = JOULE.dividedBy(COULOMB) + .withName(NameSymbol.of("volt", "V")); + public static final LinearUnit FARAD = COULOMB.dividedBy(VOLT) + .withName(NameSymbol.of("farad", "F")); + public static final LinearUnit OHM = VOLT.dividedBy(AMPERE) + .withName(NameSymbol.of("ohm", "\u03A9")); // omega + public static final LinearUnit SIEMENS = ONE.dividedBy(OHM) + .withName(NameSymbol.of("siemens", "S")); + public static final LinearUnit WEBER = VOLT.times(SECOND) + .withName(NameSymbol.of("weber", "Wb")); + public static final LinearUnit TESLA = WEBER.dividedBy(METRE.times(METRE)) + .withName(NameSymbol.of("tesla", "T")); + public static final LinearUnit HENRY = WEBER.dividedBy(AMPERE) + .withName(NameSymbol.of("henry", "H")); + public static final LinearUnit LUMEN = CANDELA.times(STERADIAN) + .withName(NameSymbol.of("lumen", "lm")); + public static final LinearUnit LUX = LUMEN.dividedBy(METRE.times(METRE)) + .withName(NameSymbol.of("lux", "lx")); + public static final LinearUnit BEQUEREL = ONE.dividedBy(SECOND) + .withName(NameSymbol.of("bequerel", "Bq")); // for activity referred to a nucleotide - public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM).withName(NameSymbol.of("grey", "Gy")); + public static final LinearUnit GRAY = JOULE.dividedBy(KILOGRAM) + .withName(NameSymbol.of("grey", "Gy")); // for absorbed dose - public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM).withName(NameSymbol.of("sievert", "Sv")); + public static final LinearUnit SIEVERT = JOULE.dividedBy(KILOGRAM) + .withName(NameSymbol.of("sievert", "Sv")); // for dose equivalent - public static final LinearUnit KATAL = MOLE.dividedBy(SECOND).withName(NameSymbol.of("katal", "kat")); - + public static final LinearUnit KATAL = MOLE.dividedBy(SECOND) + .withName(NameSymbol.of("katal", "kat")); + // common derived units included for convenience - public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000).withName(NameSymbol.of("gram", "g")); + public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000) + .withName(NameSymbol.of("gram", "g")); public static final LinearUnit SQUARE_METRE = METRE.toExponent(2) - .withName(NameSymbol.of("square metre", "m^2", "square meter", "metre squared", "meter squared")); + .withName(NameSymbol.of("square metre", "m^2", "square meter", + "metre squared", "meter squared")); public static final LinearUnit CUBIC_METRE = METRE.toExponent(3) - .withName(NameSymbol.of("cubic metre", "m^3", "cubic meter", "metre cubed", "meter cubed")); + .withName(NameSymbol.of("cubic metre", "m^3", "cubic meter", + "metre cubed", "meter cubed")); public static final LinearUnit METRE_PER_SECOND = METRE.dividedBy(SECOND) - .withName(NameSymbol.of("metre per second", "m/s", "meter per second")); - + .withName( + NameSymbol.of("metre per second", "m/s", "meter per second")); + // Non-SI units included for convenience public static final Unit CELSIUS = Unit - .fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15, tempC -> tempC + 273.15) + .fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15, + tempC -> tempC + 273.15) .withName(NameSymbol.of("degree Celsius", "\u00B0C")); - public static final LinearUnit MINUTE = SECOND.times(60).withName(NameSymbol.of("minute", "min")); - public static final LinearUnit HOUR = MINUTE.times(60).withName(NameSymbol.of("hour", "h", "hr")); - public static final LinearUnit DAY = HOUR.times(60).withName(NameSymbol.of("day", "d")); - public static final LinearUnit KILOMETRE_PER_HOUR = METRE.times(1000).dividedBy(HOUR) - .withName(NameSymbol.of("kilometre per hour", "km/h", "kilometer per hour")); + public static final LinearUnit MINUTE = SECOND.times(60) + .withName(NameSymbol.of("minute", "min")); + public static final LinearUnit HOUR = MINUTE.times(60) + .withName(NameSymbol.of("hour", "h", "hr")); + public static final LinearUnit DAY = HOUR.times(60) + .withName(NameSymbol.of("day", "d")); + public static final LinearUnit KILOMETRE_PER_HOUR = METRE.times(1000) + .dividedBy(HOUR).withName(NameSymbol.of("kilometre per hour", "km/h", + "kilometer per hour")); public static final LinearUnit DEGREE = RADIAN.times(360 / (2 * Math.PI)) .withName(NameSymbol.of("degree", "\u00B0", "deg")); - public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60).withName(NameSymbol.of("arcminute", "arcmin")); - public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60).withName(NameSymbol.of("arcsecond", "arcsec")); - public static final LinearUnit ASTRONOMICAL_UNIT = METRE.times(149597870700.0) + public static final LinearUnit ARCMINUTE = DEGREE.dividedBy(60) + .withName(NameSymbol.of("arcminute", "arcmin")); + public static final LinearUnit ARCSECOND = ARCMINUTE.dividedBy(60) + .withName(NameSymbol.of("arcsecond", "arcsec")); + public static final LinearUnit ASTRONOMICAL_UNIT = METRE + .times(149597870700.0) .withName(NameSymbol.of("astronomical unit", "au")); - public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT.dividedBy(ARCSECOND) - .withName(NameSymbol.of("parsec", "pc")); - public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0).withName(NameSymbol.of("hectare", "ha")); - public static final LinearUnit LITRE = METRE.times(METRE).times(METRE).dividedBy(1000.0) - .withName(NameSymbol.of("litre", "L", "l", "liter")); - public static final LinearUnit TONNE = KILOGRAM.times(1000.0).withName(NameSymbol.of("tonne", "t", "metric ton")); + public static final LinearUnit PARSEC = ASTRONOMICAL_UNIT + .dividedBy(ARCSECOND).withName(NameSymbol.of("parsec", "pc")); + public static final LinearUnit HECTARE = METRE.times(METRE).times(10000.0) + .withName(NameSymbol.of("hectare", "ha")); + public static final LinearUnit LITRE = METRE.times(METRE).times(METRE) + .dividedBy(1000.0).withName(NameSymbol.of("litre", "L", "l", "liter")); + public static final LinearUnit TONNE = KILOGRAM.times(1000.0) + .withName(NameSymbol.of("tonne", "t", "metric ton")); public static final LinearUnit DALTON = KILOGRAM.times(1.660539040e-27) - .withName(NameSymbol.of("dalton", "Da", "atomic unit", "u")); // approximate value + .withName(NameSymbol.of("dalton", "Da", "atomic unit", "u")); // approximate + // value public static final LinearUnit ELECTRONVOLT = JOULE.times(1.602176634e-19) .withName(NameSymbol.of("electron volt", "eV")); - public static final LinearUnit BYTE = BIT.times(8).withName(NameSymbol.of("byte", "B")); - public static final Unit NEPER = Unit - .fromConversionFunctions(ONE.getBase(), pr -> 0.5 * Math.log(pr), Np -> Math.exp(2 * Np)) + public static final LinearUnit BYTE = BIT.times(8) + .withName(NameSymbol.of("byte", "B")); + public static final Unit NEPER = Unit.fromConversionFunctions(ONE.getBase(), + pr -> 0.5 * Math.log(pr), Np -> Math.exp(2 * Np)) .withName(NameSymbol.of("neper", "Np")); - public static final Unit BEL = Unit - .fromConversionFunctions(ONE.getBase(), pr -> Math.log10(pr), dB -> Math.pow(10, dB)) + public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(), + pr -> Math.log10(pr), dB -> Math.pow(10, dB)) .withName(NameSymbol.of("bel", "B")); public static final Unit DECIBEL = Unit - .fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr), dB -> Math.pow(10, dB / 10)) + .fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr), + dB -> Math.pow(10, dB / 10)) .withName(NameSymbol.of("decibel", "dB")); - + /// The prefixes of the SI // expanding decimal prefixes - public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3).withName(NameSymbol.of("kilo", "k", "K")); - public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6).withName(NameSymbol.of("mega", "M")); - public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9).withName(NameSymbol.of("giga", "G")); - public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12).withName(NameSymbol.of("tera", "T")); - public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15).withName(NameSymbol.of("peta", "P")); - public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18).withName(NameSymbol.of("exa", "E")); - public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21).withName(NameSymbol.of("zetta", "Z")); - public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24).withName(NameSymbol.of("yotta", "Y")); - + public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3) + .withName(NameSymbol.of("kilo", "k", "K")); + public static final UnitPrefix MEGA = UnitPrefix.valueOf(1e6) + .withName(NameSymbol.of("mega", "M")); + public static final UnitPrefix GIGA = UnitPrefix.valueOf(1e9) + .withName(NameSymbol.of("giga", "G")); + public static final UnitPrefix TERA = UnitPrefix.valueOf(1e12) + .withName(NameSymbol.of("tera", "T")); + public static final UnitPrefix PETA = UnitPrefix.valueOf(1e15) + .withName(NameSymbol.of("peta", "P")); + public static final UnitPrefix EXA = UnitPrefix.valueOf(1e18) + .withName(NameSymbol.of("exa", "E")); + public static final UnitPrefix ZETTA = UnitPrefix.valueOf(1e21) + .withName(NameSymbol.of("zetta", "Z")); + public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24) + .withName(NameSymbol.of("yotta", "Y")); + // contracting decimal prefixes - public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3).withName(NameSymbol.of("milli", "m")); - public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6).withName(NameSymbol.of("micro", "\u03BC", "u")); // mu - public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9).withName(NameSymbol.of("nano", "n")); - public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12).withName(NameSymbol.of("pico", "p")); - public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15).withName(NameSymbol.of("femto", "f")); - public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18).withName(NameSymbol.of("atto", "a")); - public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21).withName(NameSymbol.of("zepto", "z")); - public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24).withName(NameSymbol.of("yocto", "y")); - + public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3) + .withName(NameSymbol.of("milli", "m")); + public static final UnitPrefix MICRO = UnitPrefix.valueOf(1e-6) + .withName(NameSymbol.of("micro", "\u03BC", "u")); // mu + public static final UnitPrefix NANO = UnitPrefix.valueOf(1e-9) + .withName(NameSymbol.of("nano", "n")); + public static final UnitPrefix PICO = UnitPrefix.valueOf(1e-12) + .withName(NameSymbol.of("pico", "p")); + public static final UnitPrefix FEMTO = UnitPrefix.valueOf(1e-15) + .withName(NameSymbol.of("femto", "f")); + public static final UnitPrefix ATTO = UnitPrefix.valueOf(1e-18) + .withName(NameSymbol.of("atto", "a")); + public static final UnitPrefix ZEPTO = UnitPrefix.valueOf(1e-21) + .withName(NameSymbol.of("zepto", "z")); + public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24) + .withName(NameSymbol.of("yocto", "y")); + // prefixes that don't match the pattern of thousands - public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1).withName(NameSymbol.of("deka", "da", "deca", "D")); - public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2).withName(NameSymbol.of("hecto", "h", "H", "hekto")); - public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1).withName(NameSymbol.of("deci", "d")); - public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2).withName(NameSymbol.of("centi", "c")); - public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024).withName(NameSymbol.of("kibi", "Ki")); - public static final UnitPrefix MEBI = KIBI.times(1024).withName(NameSymbol.of("mebi", "Mi")); - public static final UnitPrefix GIBI = MEBI.times(1024).withName(NameSymbol.of("gibi", "Gi")); - public static final UnitPrefix TEBI = GIBI.times(1024).withName(NameSymbol.of("tebi", "Ti")); - public static final UnitPrefix PEBI = TEBI.times(1024).withName(NameSymbol.of("pebi", "Pi")); - public static final UnitPrefix EXBI = PEBI.times(1024).withName(NameSymbol.of("exbi", "Ei")); - + public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1) + .withName(NameSymbol.of("deka", "da", "deca", "D")); + public static final UnitPrefix HECTO = UnitPrefix.valueOf(1e2) + .withName(NameSymbol.of("hecto", "h", "H", "hekto")); + public static final UnitPrefix DECI = UnitPrefix.valueOf(1e-1) + .withName(NameSymbol.of("deci", "d")); + public static final UnitPrefix CENTI = UnitPrefix.valueOf(1e-2) + .withName(NameSymbol.of("centi", "c")); + public static final UnitPrefix KIBI = UnitPrefix.valueOf(1024) + .withName(NameSymbol.of("kibi", "Ki")); + public static final UnitPrefix MEBI = KIBI.times(1024) + .withName(NameSymbol.of("mebi", "Mi")); + public static final UnitPrefix GIBI = MEBI.times(1024) + .withName(NameSymbol.of("gibi", "Gi")); + public static final UnitPrefix TEBI = GIBI.times(1024) + .withName(NameSymbol.of("tebi", "Ti")); + public static final UnitPrefix PEBI = TEBI.times(1024) + .withName(NameSymbol.of("pebi", "Pi")); + public static final UnitPrefix EXBI = PEBI.times(1024) + .withName(NameSymbol.of("exbi", "Ei")); + + // sets of prefixes + public static final Set ALL_PREFIXES = new HashSet<>( + Arrays.asList(DEKA, HECTO, KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, + YOTTA, DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, + YOCTO, KIBI, MEBI, GIBI, TEBI, PEBI, EXBI)); + public static final Set DECIMAL_PREFIXES = new HashSet<>( + Arrays.asList(DEKA, HECTO, KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, + YOTTA, DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, + YOCTO)); + public static final Set THOUSAND_PREFIXES = new HashSet<>( + Arrays.asList(KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, MILLI, + MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO)); + public static final Set MAGNIFYING_PREFIXES = new HashSet<>( + Arrays.asList(DEKA, HECTO, KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, + YOTTA, KIBI, MEBI, GIBI, TEBI, PEBI, EXBI)); + public static final Set REDUCING_PREFIXES = new HashSet<>( + Arrays.asList(DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, + ZEPTO, YOCTO)); + // You may NOT get SI instances! private SI() { throw new AssertionError(); diff --git a/src/org/unitConverter/unit/UnitDatabase.java b/src/org/unitConverter/unit/UnitDatabase.java index c5432f7..56846a1 100644 --- a/src/org/unitConverter/unit/UnitDatabase.java +++ b/src/org/unitConverter/unit/UnitDatabase.java @@ -42,6 +42,7 @@ import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.unitConverter.math.ConditionalExistenceCollections; import org.unitConverter.math.DecimalComparison; import org.unitConverter.math.ExpressionParser; import org.unitConverter.math.ObjectProduct; @@ -1178,6 +1179,18 @@ public final class UnitDatabase { */ private final Map units; + /** + * The rule that specifies when prefix repetition is allowed. It takes in one + * argument: a list of the prefixes being applied to the unit + * + * The prefixes are inputted in application order. This means that + * testing whether "kilomegagigametre" is a valid unit is equivalent to + * running the following code (assuming all variables are defined correctly): + *
+ * {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))} + */ + private Predicate> prefixRepetitionRule; + /** * A parser that can parse unit expressions. * @@ -1240,10 +1253,25 @@ public final class UnitDatabase { * @since v0.1.0 */ public UnitDatabase() { + this(prefixes -> true); + } + + /** + * Creates the {@code UnitsDatabase} + * + * @param prefixRepetitionRule the rule that determines when prefix + * repetition is allowed + * @since 2020-08-26 + */ + public UnitDatabase(Predicate
> prefixRepetitionRule) { this.prefixlessUnits = new HashMap<>(); this.prefixes = new HashMap<>(); this.dimensions = new HashMap<>(); - this.units = new PrefixedUnitMap(this.prefixlessUnits, this.prefixes); + this.prefixRepetitionRule = prefixRepetitionRule; + this.units = ConditionalExistenceCollections.conditionalExistenceMap( + new PrefixedUnitMap(this.prefixlessUnits, this.prefixes), + entry -> this.prefixRepetitionRule + .test(this.getPrefixesFromName(entry.getKey()))); } /** @@ -1644,6 +1672,47 @@ public final class UnitDatabase { } } + /** + * Gets all of the prefixes that are on a unit name, in application order. + * + * @param unitName name of unit + * @return prefixes + * @since 2020-08-26 + */ + List
getPrefixesFromName(final String unitName) { + if (this.prefixlessUnits.containsKey(unitName)) + return new ArrayList<>(); + else { + // find the longest prefix + 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.containsUnitName(rest) + && this.getUnit(rest) instanceof LinearUnit) { + longestPrefix = prefixName; + longestLength = prefixName.length(); + } + } + } + + final List prefixes = this + .getPrefixesFromName(unitName.substring(longestLength)); + prefixes.add(this.getPrefix(longestPrefix)); + return prefixes; + } + } + /** * Gets a unit prefix from a prefix expression * @@ -1678,6 +1747,14 @@ public final class UnitDatabase { return this.prefixExpressionParser.parseExpression(modifiedExpression); } + /** + * @return the prefixRepetitionRule + * @since 2020-08-26 + */ + public final Predicate
> getPrefixRepetitionRule() { + return this.prefixRepetitionRule; + } + /** * Gets a unit from the database from its name, looking for prefixes. * @@ -1862,6 +1939,15 @@ public final class UnitDatabase { return Collections.unmodifiableMap(this.prefixes); } + /** + * @param prefixRepetitionRule the prefixRepetitionRule to set + * @since 2020-08-26 + */ + public final void setPrefixRepetitionRule( + Predicate
> prefixRepetitionRule) { + this.prefixRepetitionRule = prefixRepetitionRule; + } + /** * @return a string stating the number of units, prefixes and dimensions in * the database diff --git a/src/org/unitConverter/unit/UnitDatabaseTest.java b/src/org/unitConverter/unit/UnitDatabaseTest.java index 164172b..96a0b83 100644 --- a/src/org/unitConverter/unit/UnitDatabaseTest.java +++ b/src/org/unitConverter/unit/UnitDatabaseTest.java @@ -18,17 +18,22 @@ 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.NoSuchElementException; import org.junit.jupiter.api.Test; /** - * A test for the {@link UnitDatabase} class. This is NOT part of this program's public API. + * A test for the {@link UnitDatabase} class. This is NOT part of this program's + * public API. * * @author Adrien Hopkins * @since 2019-04-14 @@ -39,23 +44,26 @@ class UnitDatabaseTest { 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 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); - + + 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}. + * Confirms that operations that shouldn't function for infinite databases + * throw an {@code IllegalStateException}. * * @since 2019-05-03 */ @@ -63,14 +71,14 @@ class UnitDatabaseTest { 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 { @@ -84,7 +92,7 @@ class UnitDatabaseTest { } } } - + { boolean exceptionThrown = false; try { @@ -99,7 +107,7 @@ class UnitDatabaseTest { } } } - + /** * Test that prefixes correctly apply to units. * @@ -109,23 +117,28 @@ class UnitDatabaseTest { @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); - + + // test the getPrefixesFromName method + final List
expected = Arrays.asList(C, B, A); + assertEquals(expected, database.getPrefixesFromName("ABCU")); + // 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); + assertEquals(A.getMultiplier() * B.getMultiplier() * C.getMultiplier(), + abcu.getConversionFactor(), 1e-15); } - + /** * Tests the functionnalites of the prefixless unit map. * @@ -140,21 +153,22 @@ class UnitDatabaseTest { 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. + * Tests that the database correctly stores and retrieves units, ignoring + * prefixes. * * @since 2019-04-14 * @since v0.2.0 @@ -162,18 +176,18 @@ class UnitDatabaseTest { @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")); + assertThrows(NoSuchElementException.class, () -> database.getUnit("Z")); } - + /** * Test that unit expressions return the correct value. * @@ -184,30 +198,31 @@ class UnitDatabaseTest { 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 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 * @@ -218,59 +233,64 @@ class UnitDatabaseTest { 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(); - + + 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)) { + 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. + * 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. + * 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 @@ -280,28 +300,28 @@ class UnitDatabaseTest { 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); } } -- cgit v1.2.3 From 0245594222bfa0bd9a47d8326ed323c7356ac27c Mon Sep 17 00:00:00 2001 From: Adrien HopkinsDate: Thu, 27 Aug 2020 07:06:35 -0500 Subject: Added Complex Repetition. --- .../converterGUI/DefaultPrefixRepetitionRule.java | 57 +++- .../converterGUI/UnitConverterGUI.java | 6 +- .../math/ConditionalExistenceCollections.java | 295 +++++++++++++-------- src/org/unitConverter/unit/UnitDatabase.java | 43 ++- src/org/unitConverter/unit/UnitDatabaseTest.java | 44 +-- 5 files changed, 268 insertions(+), 177 deletions(-) (limited to 'src/org/unitConverter/unit/UnitDatabase.java') diff --git a/src/org/unitConverter/converterGUI/DefaultPrefixRepetitionRule.java b/src/org/unitConverter/converterGUI/DefaultPrefixRepetitionRule.java index 34d8467..bdc3a2e 100644 --- a/src/org/unitConverter/converterGUI/DefaultPrefixRepetitionRule.java +++ b/src/org/unitConverter/converterGUI/DefaultPrefixRepetitionRule.java @@ -6,6 +6,7 @@ package org.unitConverter.converterGUI; import java.util.List; import java.util.function.Predicate; +import org.unitConverter.unit.SI; import org.unitConverter.unit.UnitPrefix; /** @@ -35,8 +36,60 @@ enum DefaultPrefixRepetitionRule implements Predicate > { COMPLEX_REPETITION { @Override public boolean test(List
prefixes) { - // TODO method stub - return false; + // determine whether we are magnifying or reducing + final boolean magnifying; + if (prefixes.isEmpty()) + return true; + else if (prefixes.get(0).getMultiplier() > 1) { + magnifying = true; + } else { + magnifying = false; + } + + // if the first prefix is non-metric (including binary prefixes), + // assume we are using non-metric prefixes + // non-metric prefixes are allowed, but can't be repeated. + if (!SI.DECIMAL_PREFIXES.contains(prefixes.get(0))) + return NO_REPETITION.test(prefixes); + + int part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto, + // 2=deka,hecto,deci,centi + + for (final UnitPrefix prefix : prefixes) { + // check that the current prefix is metric and appropriately + // magnifying/reducing + if (!SI.DECIMAL_PREFIXES.contains(prefix)) + return false; + if (magnifying != prefix.getMultiplier() > 1) + return false; + + // check if the current prefix is correct + // since part is set *after* this check, part designates the state + // of the *previous* prefix + switch (part) { + case 0: + // do nothing, any prefix is valid after a yotta + break; + case 1: + // after a kilo-zetta, only deka/hecto are valid + if (SI.THOUSAND_PREFIXES.contains(prefix)) + return false; + break; + case 2: + // deka/hecto must be the last prefix, so this is always invalid + return false; + } + + // set part + if (SI.YOTTA.equals(prefix) || SI.YOCTO.equals(prefix)) { + part = 0; + } else if (SI.THOUSAND_PREFIXES.contains(prefix)) { + part = 1; + } else { + part = 2; + } + } + return true; } }; } diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java index eff0c47..5fe4ee5 100644 --- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java +++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java @@ -1024,8 +1024,10 @@ final class UnitConverterGUI { .build()); final JRadioButton customRepetition = new JRadioButton( - "Custom Repetition Rule"); - customRepetition.setEnabled(false); + "Complex Repetition"); + customRepetition.addActionListener( + e -> this.presenter.setPrefixRepetitionRule( + DefaultPrefixRepetitionRule.COMPLEX_REPETITION)); prefixRuleButtons.add(customRepetition); prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2) diff --git a/src/org/unitConverter/math/ConditionalExistenceCollections.java b/src/org/unitConverter/math/ConditionalExistenceCollections.java index 9522885..ac1c0cf 100644 --- a/src/org/unitConverter/math/ConditionalExistenceCollections.java +++ b/src/org/unitConverter/math/ConditionalExistenceCollections.java @@ -30,20 +30,25 @@ 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 + * 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 + * 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. + * 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. + * 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 @@ -56,13 +61,13 @@ public final class ConditionalExistenceCollections { * * @author Adrien Hopkins * @since 2019-10-17 - * @param
- * type of element in collection + * @param type of element in collection */ - static final class ConditionalExistenceCollection extends AbstractCollection { + static final class ConditionalExistenceCollection + extends AbstractCollection { final Collection collection; final Predicate existenceCondition; - + /** * Creates the {@code ConditionalExistenceCollection}. * @@ -70,67 +75,89 @@ public final class ConditionalExistenceCollections { * @param existenceCondition * @since 2019-10-17 */ - private ConditionalExistenceCollection(final Collection collection, final Predicate existenceCondition) { + private ConditionalExistenceCollection(final Collection collection, + final Predicate existenceCondition) { this.collection = collection; this.existenceCondition = existenceCondition; } - + @Override public boolean add(final E e) { return this.collection.add(e) && this.existenceCondition.test(e); } - + @Override public void clear() { this.collection.clear(); } - + @Override public boolean contains(final Object o) { if (!this.collection.contains(o)) return false; - + // this collection can only contain instances of E - // since the object is in the collection, we know that it must be an instance of E + // since the object is in the collection, we know that it must be an + // instance of E // therefore this cast will always work @SuppressWarnings("unchecked") final E e = (E) o; - + return this.existenceCondition.test(e); } - + @Override public Iterator iterator() { - return conditionalExistenceIterator(this.collection.iterator(), this.existenceCondition); + return conditionalExistenceIterator(this.collection.iterator(), + this.existenceCondition); } - + @Override public boolean remove(final Object o) { - // remove() must be first in the && statement, otherwise it may not execute + // remove() must be first in the && statement, otherwise it may not + // execute final boolean containedObject = this.contains(o); return this.collection.remove(o) && containedObject; } - + @Override public int size() { - return (int) this.collection.stream().filter(this.existenceCondition).count(); + return (int) this.collection.stream().filter(this.existenceCondition) + .count(); + } + + @Override + public Object[] toArray() { + // ensure the toArray operation is supported + this.collection.toArray(); + + // if it works, do it for real + return super.toArray(); + } + + @Override + public T[] toArray(T[] a) { + // ensure the toArray operation is supported + this.collection.toArray(); + + // if it works, do it for real + return super.toArray(a); } } - + /** * 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 + * @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}. * @@ -138,12 +165,13 @@ public final class ConditionalExistenceCollections { * @param condition * @since 2019-10-17 */ - private ConditionalExistenceIterator(final Iterator iterator, final Predicate condition) { + 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. * @@ -160,12 +188,12 @@ public final class ConditionalExistenceCollections { } while (!this.existenceCondition.test(this.nextElement)); this.hasNext = true; } - + @Override public boolean hasNext() { return this.hasNext; } - + @Override public E next() { if (this.hasNext()) { @@ -175,27 +203,25 @@ public final class ConditionalExistenceCollections { } 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 + * @param key type + * @param value type */ static final class ConditionalExistenceMap extends AbstractMap { Map map; Predicate > entryExistenceCondition; - + /** * Creates the {@code ConditionalExistenceMap}. * @@ -203,205 +229,240 @@ public final class ConditionalExistenceCollections { * @param entryExistenceCondition * @since 2019-10-17 */ - private ConditionalExistenceMap(final Map map, final Predicate > entryExistenceCondition) { + private ConditionalExistenceMap(final Map map, + final Predicate > entryExistenceCondition) { this.map = map; this.entryExistenceCondition = entryExistenceCondition; } - + @Override public boolean containsKey(final Object key) { if (!this.map.containsKey(key)) return false; - + // only instances of K have mappings in the backing map // since we know that key is a valid key, it must be an instance of K @SuppressWarnings("unchecked") final K keyAsK = (K) key; - + // get and test entry final V value = this.map.get(key); final Entry entry = new SimpleEntry<>(keyAsK, value); return this.entryExistenceCondition.test(entry); } - + @Override public Set > entrySet() { - return conditionalExistenceSet(this.map.entrySet(), this.entryExistenceCondition); + return conditionalExistenceSet(this.map.entrySet(), + this.entryExistenceCondition); } - + @Override public V get(final Object key) { return this.containsKey(key) ? this.map.get(key) : null; } - + + private final Entry getEntry(K key) { + return new Entry () { + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return ConditionalExistenceMap.this.map.get(key); + } + + @Override + public V setValue(V value) { + return ConditionalExistenceMap.this.map.put(key, value); + } + }; + } + @Override public Set keySet() { - // maybe change this to use ConditionalExistenceSet - return super.keySet(); + return conditionalExistenceSet(super.keySet(), + k -> this.entryExistenceCondition.test(this.getEntry(k))); } - + @Override public V put(final K key, final V value) { final V oldValue = this.map.put(key, value); - + // get and test entry final Entry entry = new SimpleEntry<>(key, oldValue); return this.entryExistenceCondition.test(entry) ? oldValue : null; } - + @Override public V remove(final Object key) { final V oldValue = this.map.remove(key); return this.containsKey(key) ? oldValue : null; } - + @Override public Collection values() { // maybe change this to use ConditionalExistenceCollection return super.values(); } - } - + /** * Elements in this set only exist if a certain condition is true. * * @author Adrien Hopkins * @since 2019-10-17 - * @param - * type of element in set + * @param type of element in set */ static final class ConditionalExistenceSet extends AbstractSet { private final Set set; private final Predicate existenceCondition; - + /** * Creates the {@code ConditionalNonexistenceSet}. * - * @param set - * set to use - * @param existenceCondition - * condition where element exists + * @param set set to use + * @param existenceCondition condition where element exists * @since 2019-10-17 */ - private ConditionalExistenceSet(final Set set, final Predicate existenceCondition) { + private ConditionalExistenceSet(final Set set, + final Predicate existenceCondition) { this.set = set; this.existenceCondition = existenceCondition; } - + /** * {@inheritDoc} * - * Note that this method returns {@code false} if {@code e} does not pass the existence condition. + * Note that this method returns {@code false} if {@code e} does not pass + * the existence condition. */ @Override public boolean add(final E e) { return this.set.add(e) && this.existenceCondition.test(e); } - + @Override public void clear() { this.set.clear(); } - + @Override public boolean contains(final Object o) { if (!this.set.contains(o)) return false; - + // this set can only contain instances of E - // since the object is in the set, we know that it must be an instance of E + // since the object is in the set, we know that it must be an instance + // of E // therefore this cast will always work @SuppressWarnings("unchecked") final E e = (E) o; - + return this.existenceCondition.test(e); } - + @Override public Iterator
iterator() { - return conditionalExistenceIterator(this.set.iterator(), this.existenceCondition); + return conditionalExistenceIterator(this.set.iterator(), + this.existenceCondition); } - + @Override public boolean remove(final Object o) { - // remove() must be first in the && statement, otherwise it may not execute + // remove() must be first in the && statement, otherwise it may not + // execute final boolean containedObject = this.contains(o); return this.set.remove(o) && containedObject; } - + @Override public int size() { return (int) this.set.stream().filter(this.existenceCondition).count(); } + + @Override + public Object[] toArray() { + // ensure the toArray operation is supported + this.set.toArray(); + + // if it works, do it for real + return super.toArray(); + } + + @Override + public T[] toArray(T[] a) { + // ensure the toArray operation is supported + this.set.toArray(); + + // if it works, do it for real + return super.toArray(a); + } } - + /** - * Elements in the returned wrapper collection are ignored if they don't pass a condition. + * Elements in the returned wrapper collection are ignored if they don't pass + * a condition. * - * @param - * type of elements in collection - * @param collection - * collection to wrap - * @param existenceCondition - * elements only exist if this returns true + * @param type of elements in collection + * @param collection collection to wrap + * @param existenceCondition elements only exist if this returns true * @return wrapper collection * @since 2019-10-17 */ - public static final Collection conditionalExistenceCollection(final Collection collection, + public static final Collection