diff options
Diffstat (limited to 'src/main')
42 files changed, 1679 insertions, 1353 deletions
diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index da66b4c..9c23c49 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-2023 Adrien Hopkins + * Copyright (C) 2021-2024 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 @@ -25,15 +25,15 @@ import sevenUnits.utils.SemanticVersionNumber; * @since 2021-06-28 */ public final class ProgramInfo { - - /** The version number (0.4.1) */ + + /** The version number (0.5.0) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber - .stableVersion(0, 4, 1); - + .stableVersion(0, 5, 0); + private ProgramInfo() { // this class is only for static variables, you shouldn't be able to // construct an instance throw new AssertionError(); } - + } diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java index 820d48c..3f1f75f 100644 --- a/src/main/java/sevenUnits/unit/BaseDimension.java +++ b/src/main/java/sevenUnits/unit/BaseDimension.java @@ -39,7 +39,7 @@ public final class BaseDimension implements Nameable { public static BaseDimension valueOf(final String name, final String symbol) { return new BaseDimension(name, symbol); } - + /** * The name of the dimension. */ @@ -49,7 +49,7 @@ public final class BaseDimension implements Nameable { * or two characters. */ private final String symbol; - + /** * Creates the {@code BaseDimension}. * @@ -62,7 +62,7 @@ public final class BaseDimension implements Nameable { this.name = Objects.requireNonNull(name, "name must not be null."); this.symbol = Objects.requireNonNull(symbol, "symbol must not be null."); } - + /** * @since v0.4.0 */ @@ -70,7 +70,7 @@ public final class BaseDimension implements Nameable { public NameSymbol getNameSymbol() { return NameSymbol.of(this.name, this.symbol); } - + @Override public String toString() { return String.format("%s (%s)", this.name, this.symbol); diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java index dba7f52..fe85a7b 100644 --- a/src/main/java/sevenUnits/unit/BaseUnit.java +++ b/src/main/java/sevenUnits/unit/BaseUnit.java @@ -46,7 +46,7 @@ public final class BaseUnit extends Unit { final String name, final String symbol) { return new BaseUnit(dimension, name, symbol, new HashSet<>()); } - + /** * Gets a base unit from the dimension it measures, its name and its symbol. * @@ -60,12 +60,12 @@ public final class BaseUnit extends Unit { final String name, final String symbol, final Set<String> otherNames) { return new BaseUnit(dimension, name, symbol, otherNames); } - + /** * The dimension measured by this base unit. */ private final BaseDimension dimension; - + /** * Creates the {@code BaseUnit}. * @@ -81,7 +81,7 @@ public final class BaseUnit extends Unit { this.dimension = Objects.requireNonNull(dimension, "dimension must not be null."); } - + /** * Returns a {@code LinearUnit} with this unit as a base and a conversion * factor of 1. This operation must be done in order to allow units to be @@ -93,17 +93,17 @@ public final class BaseUnit extends Unit { public LinearUnit asLinearUnit() { return LinearUnit.valueOf(this.getBase(), 1); } - + @Override protected double convertFromBase(final double value) { return value; } - + @Override protected double convertToBase(final double value) { return value; } - + /** * @return dimension * @since 2019-10-16 @@ -111,7 +111,7 @@ public final class BaseUnit extends Unit { public final BaseDimension getBaseDimension() { return this.dimension; } - + @Override public String toString() { return this.getPrimaryName().orElse("Unnamed unit") @@ -119,7 +119,7 @@ public final class BaseUnit extends Unit { ? String.format(" (%s)", this.getSymbol().get()) : ""); } - + @Override public BaseUnit withName(final NameSymbol ns) { Objects.requireNonNull(ns, "ns must not be null."); diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index 0ecba6d..69a3c05 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -39,7 +39,7 @@ public final class BritishImperial { public static final LinearUnit ROOD = Length.ROD.times(Length.FURLONG); public static final LinearUnit ACRE = Length.FURLONG.times(Length.CHAIN); } - + /** * Imperial units that measure length * @@ -59,15 +59,15 @@ public final class BritishImperial { public static final LinearUnit FURLONG = CHAIN.times(10); public static final LinearUnit MILE = FURLONG.times(8); public static final LinearUnit LEAGUE = MILE.times(3); - + public static final LinearUnit NAUTICAL_MILE = Metric.METRE.times(1852); public static final LinearUnit CABLE = NAUTICAL_MILE.dividedBy(10); public static final LinearUnit FATHOM = CABLE.dividedBy(100); - + public static final LinearUnit ROD = YARD.times(5.5); public static final LinearUnit LINK = ROD.dividedBy(25); } - + /** * British Imperial units that measure mass. * @@ -85,7 +85,7 @@ public final class BritishImperial { public static final LinearUnit LONG_TON = HUNDREDWEIGHT.times(20); public static final LinearUnit SLUG = Metric.KILOGRAM.times(14.59390294); } - + /** * British Imperial units that measure volume * @@ -101,23 +101,23 @@ public final class BritishImperial { public static final LinearUnit GALLON = QUART.times(4); public static final LinearUnit PECK = GALLON.times(2); public static final LinearUnit BUSHEL = PECK.times(4); - + public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3); public static final LinearUnit CUBIC_FOOT = Length.FOOT.toExponent(3); public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3); public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT); } - + public static final LinearUnit OUNCE_FORCE = Mass.OUNCE .times(Metric.Constants.EARTH_GRAVITY); public static final LinearUnit POUND_FORCE = Mass.POUND .times(Metric.Constants.EARTH_GRAVITY); - + public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE .times(1055.06); public static final LinearUnit CALORIE = Metric.JOULE.times(4.184); public static final LinearUnit KILOCALORIE = Metric.JOULE.times(4184); - + public static final Unit FAHRENHEIT = Unit .fromConversionFunctions(Metric.KELVIN.getBase(), tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8) diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index 720b0af..6de446f 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -30,14 +30,16 @@ import sevenUnits.utils.ObjectProduct; */ final class FunctionalUnit extends Unit { /** - * A function that accepts a value expressed in the unit's base and returns that value expressed in this unit. + * A function that accepts a value expressed in the unit's base and returns + * that value expressed in this unit. * * @since 2019-05-22 */ private final DoubleUnaryOperator converterFrom; /** - * A function that accepts a value expressed in the unit and returns that value expressed in the unit's base. + * A function that accepts a value expressed in the unit and returns that + * value expressed in the unit's base. * * @since 2019-05-22 */ @@ -46,45 +48,43 @@ final class FunctionalUnit extends Unit { /** * Creates the {@code FunctionalUnit}. * - * @param base - * unit's base - * @param converterFrom - * function that accepts a value expressed in the unit's base and returns that value expressed in this - * unit. - * @param converterTo - * function that accepts a value expressed in the unit and returns that value expressed in the unit's - * base. - * @throws NullPointerException - * if any argument is null + * @param base unit's base + * @param converterFrom function that accepts a value expressed in the unit's + * base and returns that value expressed in this unit. + * @param converterTo function that accepts a value expressed in the unit + * and returns that value expressed in the unit's base. + * @throws NullPointerException if any argument is null * @since 2019-05-22 */ - public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom, + public FunctionalUnit(final ObjectProduct<BaseUnit> base, + final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo) { super(base, NameSymbol.EMPTY); - this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null."); - this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null."); + this.converterFrom = Objects.requireNonNull(converterFrom, + "converterFrom must not be null."); + this.converterTo = Objects.requireNonNull(converterTo, + "converterTo must not be null."); } /** * Creates the {@code FunctionalUnit}. * - * @param base - * unit's base - * @param converterFrom - * function that accepts a value expressed in the unit's base and returns that value expressed in this - * unit. - * @param converterTo - * function that accepts a value expressed in the unit and returns that value expressed in the unit's - * base. - * @throws NullPointerException - * if any argument is null + * @param base unit's base + * @param converterFrom function that accepts a value expressed in the unit's + * base and returns that value expressed in this unit. + * @param converterTo function that accepts a value expressed in the unit + * and returns that value expressed in the unit's base. + * @throws NullPointerException if any argument is null * @since 2019-05-22 */ - public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom, + public FunctionalUnit(final ObjectProduct<BaseUnit> base, + final DoubleUnaryOperator converterFrom, final DoubleUnaryOperator converterTo, final NameSymbol ns) { super(base, ns); - this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null."); - this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null."); + this.converterFrom = Objects.requireNonNull(converterFrom, + "converterFrom must not be null."); + this.converterTo = Objects.requireNonNull(converterTo, + "converterTo must not be null."); } /** diff --git a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java index d6046c0..e9b4d1f 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java @@ -35,13 +35,13 @@ final class FunctionalUnitlike<V> extends Unitlike<V> { * @since 2020-09-07 */ private final DoubleFunction<V> converterFrom; - + /** * A function that accepts a value in the unitlike form and returns a value * in the unitlike form's base. */ private final ToDoubleFunction<V> converterTo; - + /** * Creates the {@code FunctionalUnitlike}. * @@ -59,15 +59,15 @@ final class FunctionalUnitlike<V> extends Unitlike<V> { this.converterFrom = converterFrom; this.converterTo = converterTo; } - + @Override protected V convertFromBase(double value) { return this.converterFrom.apply(value); } - + @Override protected double convertToBase(V value) { return this.converterTo.applyAsDouble(value); } - + } diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 103b7f6..6489229 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -46,7 +46,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit and a value. For example, converts * '59 °F' to a linear unit with the value of '288.15 K' @@ -64,7 +64,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), ns); } - + /** * @return the base unit associated with {@code unit}, as a * {@code LinearUnit}. @@ -73,7 +73,7 @@ public final class LinearUnit extends Unit { public static LinearUnit getBase(final Unit unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - + /** * @return the base unit associated with {@code unitlike}, as a * {@code LinearUnit}. @@ -82,7 +82,7 @@ public final class LinearUnit extends Unit { public static LinearUnit getBase(final Unitlike<?> unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and @@ -98,7 +98,7 @@ public final class LinearUnit extends Unit { final double conversionFactor) { return new LinearUnit(unitBase, conversionFactor, NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and @@ -115,7 +115,7 @@ public final class LinearUnit extends Unit { final double conversionFactor, final NameSymbol ns) { return new LinearUnit(unitBase, conversionFactor, ns); } - + /** * The value of this unit as represented in its base form. Mathematically, * @@ -126,7 +126,7 @@ public final class LinearUnit extends Unit { * @since 2019-10-16 */ private final double conversionFactor; - + /** * Creates the {@code LinearUnit}. * @@ -139,7 +139,7 @@ public final class LinearUnit extends Unit { super(unitBase, ns); this.conversionFactor = conversionFactor; } - + /** * {@inheritDoc} * @@ -149,7 +149,7 @@ public final class LinearUnit extends Unit { protected double convertFromBase(final double value) { return value / this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} value expressed in this unit to an * {@code UncertainValue} value expressed in {@code other}. @@ -172,9 +172,9 @@ public final class LinearUnit extends Unit { else throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); - + } - + /** * {@inheritDoc} * @@ -184,7 +184,7 @@ public final class LinearUnit extends Unit { protected double convertToBase(final double value) { return value * this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} to the base unit. * @@ -193,7 +193,7 @@ public final class LinearUnit extends Unit { UncertainDouble convertToBase(final UncertainDouble value) { return value.timesExact(this.getConversionFactor()); } - + /** * Divides this unit by a scalar. * @@ -205,7 +205,7 @@ public final class LinearUnit extends Unit { public LinearUnit dividedBy(final double divisor) { return valueOf(this.getBase(), this.getConversionFactor() / divisor); } - + /** * Returns the quotient of this unit and another. * @@ -217,14 +217,14 @@ public final class LinearUnit extends Unit { */ public LinearUnit dividedBy(final LinearUnit divisor) { Objects.requireNonNull(divisor, "other must not be null"); - + // divide the units final ObjectProduct<BaseUnit> base = this.getBase() .dividedBy(divisor.getBase()); return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor()); } - + /** * {@inheritDoc} * @@ -239,7 +239,7 @@ public final class LinearUnit extends Unit { && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor()); } - + /** * @return conversion factor * @since 2019-10-16 @@ -247,7 +247,7 @@ public final class LinearUnit extends Unit { public double getConversionFactor() { return this.conversionFactor; } - + /** * {@inheritDoc} * @@ -258,7 +258,7 @@ public final class LinearUnit extends Unit { return 31 * this.getBase().hashCode() + DecimalComparison.hash(this.getConversionFactor()); } - + /** * @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there * is a {@code BaseUnit b} where @@ -268,7 +268,7 @@ public final class LinearUnit extends Unit { public boolean isBase() { return this.isCoherent() && this.getBase().isSingleObject(); } - + /** * @return whether this unit is coherent (i.e. has conversion factor 1) * @since 2019-10-16 @@ -276,7 +276,7 @@ public final class LinearUnit extends Unit { public boolean isCoherent() { return this.getConversionFactor() == 1; } - + /** * Returns the difference of this unit and another. * <p> @@ -296,18 +296,18 @@ public final class LinearUnit extends Unit { */ public LinearUnit minus(final LinearUnit subtrahend) { Objects.requireNonNull(subtrahend, "addend must not be null."); - + // reject subtrahends that cannot be added to this unit if (!this.getBase().equals(subtrahend.getBase())) throw new IllegalArgumentException(String.format( "Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahend)); - + // subtract the units return valueOf(this.getBase(), this.getConversionFactor() - subtrahend.getConversionFactor()); } - + /** * Returns the sum of this unit and another. * <p> @@ -327,18 +327,18 @@ public final class LinearUnit extends Unit { */ 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. * @@ -350,7 +350,7 @@ public final class LinearUnit extends Unit { public LinearUnit times(final double multiplier) { return valueOf(this.getBase(), this.getConversionFactor() * multiplier); } - + /** * Returns the product of this unit and another. * @@ -362,21 +362,21 @@ public final class LinearUnit extends Unit { */ public LinearUnit times(final LinearUnit multiplier) { Objects.requireNonNull(multiplier, "other must not be null"); - + // multiply the units final ObjectProduct<BaseUnit> base = this.getBase() .times(multiplier.getBase()); return valueOf(base, this.getConversionFactor() * multiplier.getConversionFactor()); } - + @Override public String toDefinitionString() { return Double.toString(this.conversionFactor) + (this.getBase().equals(ObjectProduct.empty()) ? "" : " " + this.getBase().toString(BaseUnit::getShortName)); } - + /** * Returns this unit but to an exponent. * @@ -389,12 +389,12 @@ public final class LinearUnit extends Unit { return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); } - + @Override public LinearUnit withName(final NameSymbol ns) { return valueOf(this.getBase(), this.getConversionFactor(), ns); } - + /** * Returns the result of applying {@code prefix} to this unit. * <p> @@ -413,7 +413,7 @@ public final class LinearUnit extends Unit { */ public LinearUnit withPrefix(final UnitPrefix prefix) { final LinearUnit unit = this.times(prefix.getMultiplier()); - + // create new name and symbol, if possible final String name; if (this.getPrimaryName().isPresent() @@ -422,14 +422,14 @@ public final class LinearUnit extends Unit { } else { name = null; } - + final String symbol; if (this.getSymbol().isPresent() && prefix.getSymbol().isPresent()) { symbol = prefix.getSymbol().get() + this.getSymbol().get(); } else { symbol = null; } - + return unit.withName(NameSymbol.ofNullable(name, symbol)); } } diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index f91d30b..3a9428b 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -34,7 +34,7 @@ import sevenUnits.utils.UncertainDouble; */ public final class LinearUnitValue { public static final LinearUnitValue ONE = getExact(Metric.ONE, 1); - + /** * Gets an exact {@code LinearUnitValue} * @@ -49,7 +49,7 @@ public final class LinearUnitValue { Objects.requireNonNull(unit, "unit must not be null"), UncertainDouble.of(value, 0)); } - + /** * Gets an uncertain {@code LinearUnitValue} * @@ -65,11 +65,11 @@ public final class LinearUnitValue { Objects.requireNonNull(unit, "unit must not be null"), Objects.requireNonNull(value, "value may not be null")); } - + private final LinearUnit unit; - + private final UncertainDouble value; - + /** * @param unit unit to express as * @param value value to express @@ -79,7 +79,7 @@ public final class LinearUnitValue { this.unit = unit; this.value = value; } - + /** * @return this value as a {@code UnitValue}. All uncertainty information is * removed from the returned value. @@ -88,7 +88,7 @@ public final class LinearUnitValue { public final UnitValue asUnitValue() { return UnitValue.of(this.unit, this.value.value()); } - + /** * @param other a {@code LinearUnit} * @return true iff this value can be represented with {@code other}. @@ -97,7 +97,7 @@ public final class LinearUnitValue { public final boolean canConvertTo(final LinearUnit other) { return this.unit.canConvertTo(other); } - + /** * Returns a LinearUnitValue that represents the same value expressed in a * different unit @@ -109,7 +109,7 @@ public final class LinearUnitValue { public final LinearUnitValue convertTo(final LinearUnit other) { return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); } - + /** * Divides this value by a scalar * @@ -120,7 +120,7 @@ public final class LinearUnitValue { public LinearUnitValue dividedBy(final double divisor) { return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor)); } - + /** * Divides this value by another value * @@ -132,7 +132,7 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.dividedBy(divisor.unit), this.value.dividedBy(divisor.value)); } - + /** * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 @@ -150,7 +150,7 @@ public final class LinearUnitValue { && this.unit.convertToBase(this.value) .equals(other.unit.convertToBase(other.value)); } - + /** * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 @@ -171,7 +171,7 @@ public final class LinearUnitValue { && DecimalComparison.equals(this.unit.convertToBase(this.value), other.unit.convertToBase(other.value)); } - + /** * @param other another {@code LinearUnitValue} * @return true iff this and other are within each other's uncertainty range @@ -185,10 +185,10 @@ public final class LinearUnitValue { final LinearUnit base = LinearUnit.valueOf(this.unit.getBase(), 1); final LinearUnitValue thisBase = this.convertTo(base); final LinearUnitValue otherBase = other.convertTo(base); - + return thisBase.value.equivalent(otherBase.value); } - + /** * @return the unit * @since 2020-09-29 @@ -196,7 +196,7 @@ public final class LinearUnitValue { public final LinearUnit getUnit() { return this.unit; } - + /** * @return the value * @since 2020-09-29 @@ -204,7 +204,7 @@ public final class LinearUnitValue { public final UncertainDouble getValue() { return this.value; } - + /** * @return the exact value * @since 2020-09-07 @@ -212,13 +212,13 @@ public final class LinearUnitValue { public final double getValueExact() { return this.value.value(); } - + @Override public int hashCode() { return Objects.hash(this.unit.getBase(), this.unit.convertToBase(this.getValue())); } - + /** * Returns the difference of this value and another, expressed in this * value's unit @@ -231,17 +231,17 @@ public final class LinearUnitValue { */ public LinearUnitValue minus(final LinearUnitValue subtrahend) { Objects.requireNonNull(subtrahend, "subtrahend may not be null"); - + if (!this.canConvertTo(subtrahend.unit)) throw new IllegalArgumentException(String.format( "Incompatible units for subtraction \"%s\" and \"%s\".", this.unit, subtrahend.unit)); - + final LinearUnitValue otherConverted = subtrahend.convertTo(this.unit); return LinearUnitValue.of(this.unit, this.value.minus(otherConverted.value)); } - + /** * Returns the sum of this value and another, expressed in this value's unit * @@ -253,17 +253,17 @@ public final class LinearUnitValue { */ public LinearUnitValue plus(final LinearUnitValue addend) { Objects.requireNonNull(addend, "addend may not be null"); - + if (!this.canConvertTo(addend.unit)) throw new IllegalArgumentException(String.format( "Incompatible units for addition \"%s\" and \"%s\".", this.unit, addend.unit)); - + final LinearUnitValue otherConverted = addend.convertTo(this.unit); return LinearUnitValue.of(this.unit, this.value.plus(otherConverted.value)); } - + /** * Multiplies this value by a scalar * @@ -274,7 +274,7 @@ public final class LinearUnitValue { public LinearUnitValue times(final double multiplier) { return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier)); } - + /** * Multiplies this value by another value * @@ -286,7 +286,7 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.times(multiplier.unit), this.value.times(multiplier.value)); } - + /** * Raises a value to an exponent * @@ -298,18 +298,18 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.toExponent(exponent), this.value.toExponentExact(exponent)); } - + @Override public String toString() { return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN); } - + /** * Returns a string representing the object. <br> * If the attached unit has a name or symbol, the string looks like "12 km". * Otherwise, it looks like "13 unnamed unit (= 2 m/s)". * <p> - * If showUncertainty is true, strings like "35 ± 8" are shown instead of + * If showUncertainty is true, strings like "35 � 8" are shown instead of * single numbers. * <p> * Non-exact values are rounded intelligently based on their uncertainty. @@ -321,9 +321,9 @@ public final class LinearUnitValue { final Optional<String> primaryName = this.unit.getPrimaryName(); final Optional<String> symbol = this.unit.getSymbol(); final String chosenName = symbol.orElse(primaryName.orElse(null)); - + final UncertainDouble baseValue = this.unit.convertToBase(this.value); - + // get rounded strings // if showUncertainty is true, add brackets around the string final String valueString = (showUncertainty ? "(" : "") @@ -332,7 +332,7 @@ public final class LinearUnitValue { final String baseValueString = (showUncertainty ? "(" : "") + baseValue.toString(showUncertainty, roundingMode) + (showUncertainty ? ")" : ""); - + // create string if (chosenName == null) return String.format("%s unnamed unit (= %s %s)", valueString, diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 05e82ba..7841987 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -59,13 +59,13 @@ public final class Metric { .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) @@ -89,16 +89,16 @@ public final class Metric { .valueOf(BaseDimensions.INFORMATION, "bit", "b"); public static final BaseUnit DOLLAR = BaseUnit .valueOf(BaseDimensions.CURRENCY, "dollar", "$"); - + public static final Set<BaseUnit> BASE_UNITS = Set.of(METRE, KILOGRAM, SECOND, AMPERE, KELVIN, MOLE, CANDELA, BIT); - + // You may NOT get SI.BaseUnits instances! private BaseUnits() { throw new AssertionError(); } } - + /** * Constants that relate to the SI or other systems. * @@ -109,7 +109,7 @@ public final class Metric { public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND) .dividedBy(SECOND).times(9.80665); } - + // dimensions used in the SI, as ObjectProducts public static final class Dimensions { public static final ObjectProduct<BaseDimension> EMPTY = ObjectProduct @@ -139,7 +139,7 @@ public final class Metric { public static final ObjectProduct<BaseDimension> CURRENCY = ObjectProduct .oneOf(BaseDimensions.CURRENCY) .withName(NameSymbol.ofName("Currency")); - + // derived dimensions without named SI units public static final ObjectProduct<BaseDimension> AREA = LENGTH .times(LENGTH); @@ -175,7 +175,7 @@ public final class Metric { .dividedBy(LENGTH); public static final ObjectProduct<BaseDimension> SOLID_ANGLE = AREA .dividedBy(AREA); - + // derived dimensions with named SI units public static final ObjectProduct<BaseDimension> FREQUENCY = EMPTY .dividedBy(TIME); @@ -209,17 +209,17 @@ public final class Metric { .dividedBy(MASS); public static final ObjectProduct<BaseDimension> CATALYTIC_ACTIVITY = QUANTITY .dividedBy(TIME); - + // You may NOT get SI.Dimension instances! private Dimensions() { throw new AssertionError(); } } - + /// The units of the SI public static final LinearUnit ONE = LinearUnit .valueOf(ObjectProduct.empty(), 1); - + public static final LinearUnit METRE = BaseUnits.METRE.asLinearUnit() .withName(NameSymbol.of("metre", "m", "meter")); public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit() @@ -241,7 +241,7 @@ public final class Metric { // 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) @@ -290,7 +290,7 @@ public final class Metric { // common derived units included for convenience public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000) .withName(NameSymbol.of("gram", "g")); - + public static final LinearUnit SQUARE_METRE = METRE.toExponent(2) .withName(NameSymbol.of("square metre", "m^2", "square meter", "metre squared", "meter squared")); @@ -305,7 +305,7 @@ public final class Metric { .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) @@ -349,7 +349,7 @@ public final class Metric { .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) @@ -368,7 +368,7 @@ public final class Metric { .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")); @@ -386,7 +386,7 @@ public final class Metric { .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")); @@ -408,7 +408,7 @@ public final class Metric { .withName(NameSymbol.of("pebi", "Pi")); public static final UnitPrefix EXBI = PEBI.times(1024) .withName(NameSymbol.of("exbi", "Ei")); - + // a few prefixed units public static final LinearUnit MICROMETRE = Metric.METRE .withPrefix(Metric.MICRO); @@ -418,7 +418,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAMETRE = Metric.METRE .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROLITRE = Metric.LITRE .withPrefix(Metric.MICRO); public static final LinearUnit MILLILITRE = Metric.LITRE @@ -427,7 +427,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGALITRE = Metric.LITRE .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROSECOND = Metric.SECOND .withPrefix(Metric.MICRO); public static final LinearUnit MILLISECOND = Metric.SECOND @@ -436,14 +436,14 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGASECOND = Metric.SECOND .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROGRAM = Metric.GRAM .withPrefix(Metric.MICRO); public static final LinearUnit MILLIGRAM = Metric.GRAM .withPrefix(Metric.MILLI); public static final LinearUnit MEGAGRAM = Metric.GRAM .withPrefix(Metric.MEGA); - + public static final LinearUnit MICRONEWTON = Metric.NEWTON .withPrefix(Metric.MICRO); public static final LinearUnit MILLINEWTON = Metric.NEWTON @@ -452,7 +452,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGANEWTON = Metric.NEWTON .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROJOULE = Metric.JOULE .withPrefix(Metric.MICRO); public static final LinearUnit MILLIJOULE = Metric.JOULE @@ -461,7 +461,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAJOULE = Metric.JOULE .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROWATT = Metric.WATT .withPrefix(Metric.MICRO); public static final LinearUnit MILLIWATT = Metric.WATT @@ -470,7 +470,7 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAWATT = Metric.WATT .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROCOULOMB = Metric.COULOMB .withPrefix(Metric.MICRO); public static final LinearUnit MILLICOULOMB = Metric.COULOMB @@ -479,12 +479,12 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGACOULOMB = Metric.COULOMB .withPrefix(Metric.MEGA); - + public static final LinearUnit MICROAMPERE = Metric.AMPERE .withPrefix(Metric.MICRO); public static final LinearUnit MILLIAMPERE = Metric.AMPERE .withPrefix(Metric.MILLI); - + public static final LinearUnit MICROVOLT = Metric.VOLT .withPrefix(Metric.MICRO); public static final LinearUnit MILLIVOLT = Metric.VOLT @@ -493,16 +493,16 @@ public final class Metric { .withPrefix(Metric.KILO); public static final LinearUnit MEGAVOLT = Metric.VOLT .withPrefix(Metric.MEGA); - + public static final LinearUnit KILOOHM = Metric.OHM.withPrefix(Metric.KILO); public static final LinearUnit MEGAOHM = Metric.OHM.withPrefix(Metric.MEGA); - + // sets of prefixes public static final Set<UnitPrefix> ALL_PREFIXES = Set.of(DEKA, HECTO, KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO, KIBI, MEBI, GIBI, TEBI, PEBI, EXBI); - + public static final Set<UnitPrefix> DECIMAL_PREFIXES = Set.of(DEKA, HECTO, KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); @@ -514,7 +514,7 @@ public final class Metric { TEBI, PEBI, EXBI); public static final Set<UnitPrefix> REDUCING_PREFIXES = Set.of(DECI, CENTI, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO); - + // You may NOT get SI instances! private Metric() { throw new AssertionError(); diff --git a/src/main/java/sevenUnits/unit/MultiUnit.java b/src/main/java/sevenUnits/unit/MultiUnit.java index bc240e3..950c547 100644 --- a/src/main/java/sevenUnits/unit/MultiUnit.java +++ b/src/main/java/sevenUnits/unit/MultiUnit.java @@ -39,7 +39,7 @@ public final class MultiUnit extends Unitlike<List<Double>> { public static final MultiUnit of(LinearUnit... units) { return of(Arrays.asList(units)); } - + /** * Creates a {@code MultiUnit} from its units. It will not have a name or * symbol. @@ -57,12 +57,12 @@ public final class MultiUnit extends Unitlike<List<Double>> { } return new MultiUnit(new ArrayList<>(units), unitBase, NameSymbol.EMPTY); } - + /** * The units that make up this value. */ private final List<LinearUnit> units; - + /** * Creates a {@code MultiUnit}. * @@ -73,24 +73,24 @@ public final class MultiUnit extends Unitlike<List<Double>> { super(unitBase, ns); this.units = units; } - + @Override protected List<Double> convertFromBase(double value) { final List<Double> values = new ArrayList<>(this.units.size()); double temp = value; - + for (final LinearUnit unit : this.units.subList(0, this.units.size() - 1)) { values.add(Math.floor(temp / unit.getConversionFactor())); temp %= unit.getConversionFactor(); } - + values.add(this.units.size() - 1, this.units.get(this.units.size() - 1).convertFromBase(temp)); - + return values; } - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -99,7 +99,7 @@ public final class MultiUnit extends Unitlike<List<Double>> { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -115,10 +115,10 @@ public final class MultiUnit extends Unitlike<List<Double>> { for (final double d : values) { valueList.add(d); } - + return this.convertTo(other, valueList); } - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -127,7 +127,7 @@ public final class MultiUnit extends Unitlike<List<Double>> { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -142,16 +142,16 @@ public final class MultiUnit extends Unitlike<List<Double>> { for (final double d : values) { valueList.add(d); } - + return this.convertTo(other, valueList); } - + @Override protected double convertToBase(List<Double> value) { if (value.size() != this.units.size()) throw new IllegalArgumentException("Wrong number of values for " + this.units.size() + "-unit MultiUnit."); - + double baseValue = 0; for (int i = 0; i < this.units.size(); i++) { baseValue += value.get(i) * this.units.get(i).getConversionFactor(); diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java index 459071f..fce829e 100644 --- a/src/main/java/sevenUnits/unit/USCustomary.java +++ b/src/main/java/sevenUnits/unit/USCustomary.java @@ -30,10 +30,14 @@ public final class USCustomary { * @since 2019-11-08 */ public static final class Area { - public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT.times(Length.SURVEY_FOOT); - public static final LinearUnit SQUARE_CHAIN = Length.SURVEY_CHAIN.times(Length.SURVEY_CHAIN); - public static final LinearUnit ACRE = Length.SURVEY_CHAIN.times(Length.SURVEY_FURLONG); - public static final LinearUnit SECTION = Length.SURVEY_MILE.times(Length.SURVEY_MILE); + public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT + .times(Length.SURVEY_FOOT); + public static final LinearUnit SQUARE_CHAIN = Length.SURVEY_CHAIN + .times(Length.SURVEY_CHAIN); + public static final LinearUnit ACRE = Length.SURVEY_CHAIN + .times(Length.SURVEY_FURLONG); + public static final LinearUnit SECTION = Length.SURVEY_MILE + .times(Length.SURVEY_MILE); public static final LinearUnit SURVEY_TOWNSHIP = SECTION.times(36); } @@ -52,8 +56,10 @@ public final class USCustomary { public static final LinearUnit YARD = BritishImperial.Length.YARD; public static final LinearUnit MILE = BritishImperial.Length.MILE; - public static final LinearUnit SURVEY_FOOT = Metric.METRE.times(1200.0 / 3937.0); - public static final LinearUnit SURVEY_LINK = SURVEY_FOOT.times(33.0 / 50.0); + public static final LinearUnit SURVEY_FOOT = Metric.METRE + .times(1200.0 / 3937.0); + public static final LinearUnit SURVEY_LINK = SURVEY_FOOT + .times(33.0 / 50.0); public static final LinearUnit SURVEY_ROD = SURVEY_FOOT.times(16.5); public static final LinearUnit SURVEY_CHAIN = SURVEY_ROD.times(4); public static final LinearUnit SURVEY_FURLONG = SURVEY_CHAIN.times(10); @@ -97,7 +103,8 @@ public final class USCustomary { public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3); public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT); - public static final LinearUnit MINIM = Metric.LITRE.withPrefix(Metric.MICRO).times(61.611519921875); + public static final LinearUnit MINIM = Metric.LITRE + .withPrefix(Metric.MICRO).times(61.611519921875); public static final LinearUnit FLUID_DRAM = MINIM.times(60); public static final LinearUnit TEASPOON = MINIM.times(80); public static final LinearUnit TABLESPOON = TEASPOON.times(3); @@ -112,7 +119,8 @@ public final class USCustomary { public static final LinearUnit OIL_BARREL = GALLON.times(42); public static final LinearUnit HOGSHEAD = GALLON.times(63); - public static final LinearUnit DRY_PINT = Metric.LITRE.times(0.5506104713575); + public static final LinearUnit DRY_PINT = Metric.LITRE + .times(0.5506104713575); public static final LinearUnit DRY_QUART = DRY_PINT.times(2); public static final LinearUnit DRY_GALLON = DRY_QUART.times(4); public static final LinearUnit PECK = DRY_GALLON.times(2); @@ -128,8 +136,10 @@ public final class USCustomary { public static final LinearUnit KILOCALORIE = BritishImperial.KILOCALORIE; public static final LinearUnit FOOT_POUND = POUND_FORCE.times(Length.FOOT); - public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE).dividedBy(Metric.MINUTE).times(33000); - public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE.dividedBy(Length.INCH.toExponent(2)); + public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE) + .dividedBy(Metric.MINUTE).times(33000); + public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE + .dividedBy(Length.INCH.toExponent(2)); public static final Unit FAHRENHEIT = BritishImperial.FAHRENHEIT; } diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 14478ba..59e928a 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -59,7 +59,7 @@ public abstract class Unit implements Nameable { final DoubleUnaryOperator converterTo) { return new FunctionalUnit(base, converterFrom, converterTo); } - + /** * Returns a unit from its base and the functions it uses to convert to and * from its base. @@ -87,28 +87,28 @@ public abstract class Unit implements Nameable { final DoubleUnaryOperator converterTo, final NameSymbol ns) { return new FunctionalUnit(base, converterFrom, converterTo, ns); } - + /** * The combination of units that this unit is based on. * * @since 2019-10-16 */ private final ObjectProduct<BaseUnit> unitBase; - + /** * This unit's name(s) and symbol * * @since 2020-09-07 */ private final NameSymbol nameSymbol; - + /** * Cache storing the result of getDimension() * * @since 2019-10-16 */ private transient ObjectProduct<BaseDimension> dimension = null; - + /** * A constructor that constructs {@code BaseUnit} instances. * @@ -121,7 +121,7 @@ public abstract class Unit implements Nameable { throw new AssertionError(); this.nameSymbol = nameSymbol; } - + /** * Creates the {@code Unit}. * @@ -135,7 +135,7 @@ public abstract class Unit implements Nameable { "unitBase may not be null"); this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); } - + /** * @return this unit as a {@link Unitlike} * @since 2020-09-07 @@ -144,7 +144,7 @@ public abstract class Unit implements Nameable { return Unitlike.fromConversionFunctions(this.getBase(), this::convertFromBase, this::convertToBase, this.getNameSymbol()); } - + /** * Checks if a value expressed in this unit can be converted to a value * expressed in {@code other} @@ -159,7 +159,7 @@ public abstract class Unit implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + /** * Checks if a value expressed in this unit can be converted to a value * expressed in {@code other} @@ -174,7 +174,7 @@ public abstract class Unit implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + /** * Converts from a value expressed in this unit's base unit to a value * expressed in this unit. @@ -190,14 +190,14 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in <b>base</b> unit * @return value expressed in <b>this</b> unit * @since 2018-12-22 * @since v0.1.0 */ protected abstract double convertFromBase(double value); - + /** * Converts a value expressed in this unit to a value expressed in * {@code other}. @@ -206,7 +206,7 @@ public abstract class Unit implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -224,7 +224,7 @@ public abstract class Unit implements Nameable { throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } - + /** * Converts a value expressed in this unit to a value expressed in * {@code other}. @@ -252,7 +252,7 @@ public abstract class Unit implements Nameable { 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. @@ -268,14 +268,14 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in <b>this</b> unit * @return value expressed in <b>base</b> unit * @since 2018-12-22 * @since v0.1.0 */ protected abstract double convertToBase(double value); - + /** * @return combination of units that this unit is based on * @since 2018-12-22 @@ -284,7 +284,7 @@ public abstract class Unit implements Nameable { public final ObjectProduct<BaseUnit> getBase() { return this.unitBase; } - + /** * @return dimension measured by this unit * @since 2018-12-22 @@ -294,16 +294,16 @@ public abstract class Unit implements Nameable { if (this.dimension == null) { final Map<BaseUnit, Integer> mapping = this.unitBase.exponentMap(); final Map<BaseDimension, Integer> dimensionMap = new HashMap<>(); - + for (final BaseUnit key : mapping.keySet()) { dimensionMap.put(key.getBaseDimension(), mapping.get(key)); } - + this.dimension = ObjectProduct.fromExponentMapping(dimensionMap); } return this.dimension; } - + /** * @return the nameSymbol * @since 2020-09-07 @@ -312,7 +312,7 @@ public abstract class Unit implements Nameable { public final NameSymbol getNameSymbol() { return this.nameSymbol; } - + /** * Returns true iff this unit is metric. * <p> @@ -337,18 +337,18 @@ public abstract class Unit implements Nameable { if (!(this instanceof LinearUnit)) return false; final LinearUnit linear = (LinearUnit) this; - + // second condition - check that for (final BaseUnit b : linear.getBase().getBaseSet()) { if (!Metric.BaseUnits.BASE_UNITS.contains(b)) return false; } - + // third condition - check that conversion factor is a power of 10 return DecimalComparison .equals(Math.log10(linear.getConversionFactor()) % 1.0, 0); } - + /** * @return a string representing this unit's definition * @since 2022-03-10 @@ -360,7 +360,7 @@ public abstract class Unit implements Nameable { return "derived from " + this.getBase().toString(BaseUnit::getShortName); } - + /** * @return a string containing both this unit's name and its definition * @since 2022-03-10 @@ -368,7 +368,7 @@ public abstract class Unit implements Nameable { public final String toFullString() { return this.toString() + " (" + this.toDefinitionString() + ")"; } - + @Override public String toString() { if (this.nameSymbol.getPrimaryName().isPresent() @@ -378,7 +378,7 @@ public abstract class Unit implements Nameable { else return this.getName(); } - + /** * @param ns name(s) and symbol to use * @return a copy of this unit with provided name(s) and symbol diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 12b78a7..0120067 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018-2024 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 @@ -123,7 +123,7 @@ public final class UnitDatabase { implements Entry<String, Unit> { private final String key; private final Unit value; - + /** * Creates the {@code PrefixedUnitEntry}. * @@ -136,7 +136,7 @@ public final class UnitDatabase { this.key = key; this.value = value; } - + /** * @since 2019-05-03 */ @@ -148,17 +148,17 @@ public final class UnitDatabase { return Objects.equals(this.getKey(), other.getKey()) && Objects.equals(this.getValue(), other.getValue()); } - + @Override public String getKey() { return this.key; } - + @Override public Unit getValue() { return this.value; } - + /** * @since 2019-05-03 */ @@ -168,13 +168,13 @@ public final class UnitDatabase { ^ (this.getValue() == null ? 0 : this.getValue().hashCode()); } - + @Override public Unit setValue(final Unit value) { throw new UnsupportedOperationException( "Cannot set value in an immutable entry"); } - + /** * Returns a string representation of the entry. The format of the * string is the string representation of the key, then the equals @@ -188,7 +188,7 @@ public final class UnitDatabase { return this.getKey() + "=" + this.getValue(); } } - + /** * An iterator that iterates over the units of a * {@code PrefixedUnitNameSet}. @@ -203,12 +203,12 @@ public final class UnitDatabase { private int unitNamePosition = 0; // the indices of the prefixes attached to the current unit private final List<Integer> prefixCoordinates = new ArrayList<>(); - + // values from the unit entry set private final Map<String, Unit> map; private transient final List<String> unitNames; private transient final List<String> prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -221,7 +221,7 @@ public final class UnitDatabase { this.unitNames = new ArrayList<>(map.units.keySet()); this.prefixNames = new ArrayList<>(map.prefixes.keySet()); } - + /** * @return current unit name * @since 2019-04-14 @@ -233,10 +233,10 @@ public final class UnitDatabase { unitName.append(this.prefixNames.get(i)); } unitName.append(this.unitNames.get(this.unitNamePosition)); - + return unitName.toString(); } - + @Override public boolean hasNext() { if (this.unitNames.isEmpty()) @@ -249,7 +249,7 @@ public final class UnitDatabase { return true; } } - + /** * Changes this iterator's position to the next available one. * @@ -258,11 +258,11 @@ public final class UnitDatabase { */ private void incrementPosition() { this.unitNamePosition++; - + if (this.unitNamePosition >= this.unitNames.size()) { // we have used all of our units, go to a different prefix this.unitNamePosition = 0; - + // if the prefix coordinates are empty, then set it to [0] if (this.prefixCoordinates.isEmpty()) { this.prefixCoordinates.add(0, 0); @@ -271,7 +271,7 @@ public final class UnitDatabase { int i = this.prefixCoordinates.size() - 1; this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1); - + // fix any carrying errors while (i >= 0 && this.prefixCoordinates .get(i) >= this.prefixNames.size()) { @@ -279,7 +279,7 @@ public final class UnitDatabase { this.prefixCoordinates.set(i--, 0); // null and // decrement at the // same time - + if (i < 0) { // we need to add a new coordinate this.prefixCoordinates.add(0, 0); } else { // increment an existing one @@ -290,18 +290,18 @@ public final class UnitDatabase { } } } - + @Override public Entry<String, Unit> next() { // get next element final Entry<String, Unit> nextEntry = this.peek(); - + // iterate to next position this.incrementPosition(); - + return nextEntry; } - + /** * @return the next element in the iterator, without iterating over * it @@ -310,7 +310,7 @@ public final class UnitDatabase { private Entry<String, Unit> peek() { if (!this.hasNext()) throw new NoSuchElementException("No units left!"); - + // if I have prefixes, ensure I'm not using a nonlinear unit // since all of the unprefixed stuff is done, just remove // nonlinear units @@ -321,12 +321,12 @@ public final class UnitDatabase { this.unitNames.remove(this.unitNamePosition); } } - + final String nextName = this.getCurrentUnitName(); - + return new PrefixedUnitEntry(nextName, this.map.get(nextName)); } - + /** * Returns a string representation of the object. The exact details * of the representation are unspecified and subject to change. @@ -340,10 +340,10 @@ public final class UnitDatabase { this.peek()); } } - + // the map that created this set private final PrefixedUnitMap map; - + /** * Creates the {@code PrefixedUnitNameSet}. * @@ -354,31 +354,31 @@ public final class UnitDatabase { public PrefixedUnitEntrySet(final PrefixedUnitMap map) { this.map = map; } - + @Override public boolean add(final Map.Entry<String, Unit> e) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public boolean addAll( final Collection<? extends Map.Entry<String, Unit>> c) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable set"); } - + @Override public boolean contains(final Object o) { // get the entry final Entry<String, Unit> entry; - + try { // This is OK because I'm in a try-catch block, catching the // exact exception that would be thrown. @@ -389,11 +389,11 @@ public final class UnitDatabase { throw new IllegalArgumentException( "Attempted to test for an entry using a non-entry."); } - + return this.map.containsKey(entry.getKey()) && this.map.get(entry.getKey()).equals(entry.getValue()); } - + @Override public boolean containsAll(final Collection<?> c) { for (final Object o : c) @@ -401,42 +401,42 @@ public final class UnitDatabase { return false; return true; } - + @Override public boolean isEmpty() { return this.map.isEmpty(); } - + @Override public Iterator<Entry<String, Unit>> iterator() { return new PrefixedUnitEntryIterator(this.map); } - + @Override public boolean remove(final Object o) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeIf( final Predicate<? super Entry<String, Unit>> filter) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean retainAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public int size() { if (this.map.units.isEmpty()) @@ -449,7 +449,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -462,7 +462,7 @@ public final class UnitDatabase { throw new IllegalStateException( "Cannot make an infinite set into an array."); } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -475,7 +475,7 @@ public final class UnitDatabase { throw new IllegalStateException( "Cannot make an infinite set into an array."); } - + @Override public String toString() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) @@ -486,7 +486,7 @@ public final class UnitDatabase { this.map.units, this.map.prefixes); } } - + /** * The class used for unit name sets. * @@ -518,12 +518,12 @@ public final class UnitDatabase { private int unitNamePosition = 0; // the indices of the prefixes attached to the current unit private final List<Integer> prefixCoordinates = new ArrayList<>(); - + // values from the unit name set private final Map<String, Unit> map; private transient final List<String> unitNames; private transient final List<String> prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -536,7 +536,7 @@ public final class UnitDatabase { this.unitNames = new ArrayList<>(map.units.keySet()); this.prefixNames = new ArrayList<>(map.prefixes.keySet()); } - + /** * @return current unit name * @since 2019-04-14 @@ -548,10 +548,10 @@ public final class UnitDatabase { unitName.append(this.prefixNames.get(i)); } unitName.append(this.unitNames.get(this.unitNamePosition)); - + return unitName.toString(); } - + @Override public boolean hasNext() { if (this.unitNames.isEmpty()) @@ -564,7 +564,7 @@ public final class UnitDatabase { return true; } } - + /** * Changes this iterator's position to the next available one. * @@ -573,11 +573,11 @@ public final class UnitDatabase { */ private void incrementPosition() { this.unitNamePosition++; - + if (this.unitNamePosition >= this.unitNames.size()) { // we have used all of our units, go to a different prefix this.unitNamePosition = 0; - + // if the prefix coordinates are empty, then set it to [0] if (this.prefixCoordinates.isEmpty()) { this.prefixCoordinates.add(0, 0); @@ -586,7 +586,7 @@ public final class UnitDatabase { int i = this.prefixCoordinates.size() - 1; this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1); - + // fix any carrying errors while (i >= 0 && this.prefixCoordinates .get(i) >= this.prefixNames.size()) { @@ -594,7 +594,7 @@ public final class UnitDatabase { this.prefixCoordinates.set(i--, 0); // null and // decrement at the // same time - + if (i < 0) { // we need to add a new coordinate this.prefixCoordinates.add(0, 0); } else { // increment an existing one @@ -605,16 +605,16 @@ public final class UnitDatabase { } } } - + @Override public String next() { final String nextName = this.peek(); - + this.incrementPosition(); - + return nextName; } - + /** * @return the next element in the iterator, without iterating over * it @@ -633,10 +633,10 @@ public final class UnitDatabase { this.unitNames.remove(this.unitNamePosition); } } - + return this.getCurrentUnitName(); } - + /** * Returns a string representation of the object. The exact details * of the representation are unspecified and subject to change. @@ -650,10 +650,10 @@ public final class UnitDatabase { this.peek()); } } - + // the map that created this set private final PrefixedUnitMap map; - + /** * Creates the {@code PrefixedUnitNameSet}. * @@ -664,30 +664,30 @@ public final class UnitDatabase { public PrefixedUnitNameSet(final PrefixedUnitMap map) { this.map = map; } - + @Override public boolean add(final String e) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public boolean addAll(final Collection<? extends String> c) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable set"); } - + @Override public boolean contains(final Object o) { return this.map.containsKey(o); } - + @Override public boolean containsAll(final Collection<?> c) { for (final Object o : c) @@ -695,41 +695,41 @@ public final class UnitDatabase { return false; return true; } - + @Override public boolean isEmpty() { return this.map.isEmpty(); } - + @Override public Iterator<String> iterator() { return new PrefixedUnitNameIterator(this.map); } - + @Override public boolean remove(final Object o) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeIf(final Predicate<? super String> filter) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean retainAll(final Collection<?> c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public int size() { if (this.map.units.isEmpty()) @@ -742,7 +742,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -754,9 +754,9 @@ public final class UnitDatabase { // infinite set throw new IllegalStateException( "Cannot make an infinite set into an array."); - + } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -769,7 +769,7 @@ public final class UnitDatabase { throw new IllegalStateException( "Cannot make an infinite set into an array."); } - + @Override public String toString() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) @@ -780,7 +780,7 @@ public final class UnitDatabase { this.map.units, this.map.prefixes); } } - + /** * The units stored in this collection, without prefixes. * @@ -788,7 +788,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, Unit> units; - + /** * The available prefixes for use. * @@ -796,12 +796,12 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, UnitPrefix> prefixes; - + // caches private transient Collection<Unit> values = null; private transient Set<String> keySet = null; private transient Set<Entry<String, Unit>> entrySet = null; - + /** * Creates the {@code PrefixedUnitMap}. * @@ -817,50 +817,50 @@ public final class UnitDatabase { this.units = Collections.unmodifiableMap(units); this.prefixes = Collections.unmodifiableMap(prefixes); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable map"); } - + @Override public Unit compute(final String key, final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfAbsent(final String key, final Function<? super String, ? extends Unit> mappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfPresent(final String key, final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public boolean containsKey(final Object key) { // First, test if there is a unit with the key if (this.units.containsKey(key)) return true; - + // Next, try to cast it to String if (!(key instanceof String)) throw new IllegalArgumentException( "Attempted to test for a unit using a non-string name."); final String unitName = (String) key; - + // Then, look for the longest prefix that is attached to a valid unit String longestPrefix = null; int longestLength = 0; - + for (final String prefixName : this.prefixes.keySet()) { // a prefix name is valid if: // - it is prefixed (i.e. the unit name starts with it) @@ -879,10 +879,10 @@ public final class UnitDatabase { } } } - + return longestPrefix != null; } - + /** * {@inheritDoc} * @@ -895,7 +895,7 @@ public final class UnitDatabase { public boolean containsValue(final Object value) { return this.units.containsValue(value); } - + @Override public Set<Entry<String, Unit>> entrySet() { if (this.entrySet == null) { @@ -903,23 +903,23 @@ public final class UnitDatabase { } return this.entrySet; } - + @Override public Unit get(final Object key) { // First, test if there is a unit with the key if (this.units.containsKey(key)) return this.units.get(key); - + // Next, try to cast it to String if (!(key instanceof String)) throw new IllegalArgumentException( "Attempted to obtain a unit using a non-string name."); final String unitName = (String) key; - + // Then, look for the longest prefix that is attached to a valid unit String longestPrefix = null; int longestLength = 0; - + for (final String prefixName : this.prefixes.keySet()) { // a prefix name is valid if: // - it is prefixed (i.e. the unit name starts with it) @@ -938,7 +938,7 @@ public final class UnitDatabase { } } } - + // if none found, returns null if (longestPrefix == null) return null; @@ -949,16 +949,16 @@ public final class UnitDatabase { // before selecting this prefix final LinearUnit unit = (LinearUnit) this.get(rest); final UnitPrefix prefix = this.prefixes.get(longestPrefix); - + return unit.withPrefix(prefix); } } - + @Override public boolean isEmpty() { return this.units.isEmpty(); } - + @Override public Set<String> keySet() { if (this.keySet == null) { @@ -966,64 +966,64 @@ public final class UnitDatabase { } return this.keySet; } - + @Override public Unit merge(final String key, final Unit value, final BiFunction<? super Unit, ? super Unit, ? extends Unit> remappingFunction) { throw new UnsupportedOperationException( "Cannot merge into an immutable map"); } - + @Override public Unit put(final String key, final Unit value) { throw new UnsupportedOperationException( "Cannot add entries to an immutable map"); } - + @Override public void putAll(final Map<? extends String, ? extends Unit> m) { throw new UnsupportedOperationException( "Cannot add entries to an immutable map"); } - + @Override public Unit putIfAbsent(final String key, final Unit value) { throw new UnsupportedOperationException( "Cannot add entries to an immutable map"); } - + @Override public Unit remove(final Object key) { throw new UnsupportedOperationException( "Cannot remove entries from an immutable map"); } - + @Override public boolean remove(final Object key, final Object value) { throw new UnsupportedOperationException( "Cannot remove entries from an immutable map"); } - + @Override public Unit replace(final String key, final Unit value) { throw new UnsupportedOperationException( "Cannot replace entries in an immutable map"); } - + @Override public boolean replace(final String key, final Unit oldValue, final Unit newValue) { throw new UnsupportedOperationException( "Cannot replace entries in an immutable map"); } - + @Override public void replaceAll( final BiFunction<? super String, ? super Unit, ? extends Unit> function) { throw new UnsupportedOperationException( "Cannot replace entries in an immutable map"); } - + @Override public int size() { if (this.units.isEmpty()) @@ -1036,7 +1036,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + @Override public String toString() { if (this.units.isEmpty() || this.prefixes.isEmpty()) @@ -1046,7 +1046,7 @@ public final class UnitDatabase { "Infinite map of name-unit entries created from units %s and prefixes %s", this.units, this.prefixes); } - + /** * {@inheritDoc} * @@ -1064,20 +1064,20 @@ public final class UnitDatabase { return this.values; } } - + /** * Replacements done to *all* expression types */ private static final Map<Pattern, String> EXPRESSION_REPLACEMENTS = new HashMap<>(); - + // add data to expression replacements static { // add spaces around operators - for (final String operator : Arrays.asList("\\*", "/", "\\^")) { + for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) { EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator), " " + operator + " "); } - + // replace multiple spaces with a single space EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " "); // place brackets around any expression of the form "number unit", with or @@ -1092,20 +1092,20 @@ public final class UnitDatabase { // "1e3") ), "\\($1 $2\\)"); } - + /** * A regular expression that separates names and expressions in unit files. */ private static final Pattern NAME_EXPRESSION = Pattern .compile("(\\S+)\\s+(\\S.*)"); - + /** * Like normal string comparisons, but shorter strings are always less than * longer strings. */ private static final Comparator<String> lengthFirstComparator = Comparator .comparingInt(String::length).thenComparing(Comparator.naturalOrder()); - + /** * The exponent operator * @@ -1132,7 +1132,7 @@ public final class UnitDatabase { // not a number throw new IllegalArgumentException("Exponents must be numbers."); } - + /** * The exponent operator * @@ -1158,7 +1158,7 @@ public final class UnitDatabase { // not a number throw new IllegalArgumentException("Exponents must be numbers."); } - + /** * @return true if entry represents a removable duplicate entry of map. * @since 2021-05-22 @@ -1174,7 +1174,7 @@ public final class UnitDatabase { } return false; } - + /** * The units in this system, excluding prefixes. * @@ -1182,7 +1182,7 @@ public final class UnitDatabase { * @since v0.1.0 */ private final Map<String, Unit> prefixlessUnits; - + /** * The unit prefixes in this system. * @@ -1190,7 +1190,7 @@ public final class UnitDatabase { * @since v0.1.0 */ private final Map<String, UnitPrefix> prefixes; - + /** * The dimensions in this system. * @@ -1198,7 +1198,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, ObjectProduct<BaseDimension>> dimensions; - + /** * A map mapping strings to units (including prefixes) * @@ -1206,7 +1206,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map<String, Unit> units; - + /** * The rule that specifies when prefix repetition is allowed. It takes in one * argument: a list of the prefixes being applied to the unit @@ -1218,7 +1218,7 @@ public final class UnitDatabase { * {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))} */ private Predicate<List<UnitPrefix>> prefixRepetitionRule; - + /** * A parser that can parse unit expressions. * @@ -1227,13 +1227,13 @@ public final class UnitDatabase { */ private final ExpressionParser<LinearUnit> unitExpressionParser = new ExpressionParser.Builder<>( this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2) - .build(); - + .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) + .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) + .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2).build(); + /** * A parser that can parse unit value expressions. * @@ -1241,14 +1241,15 @@ public final class UnitDatabase { */ private final ExpressionParser<LinearUnitValue> unitValueExpressionParser = new ExpressionParser.Builder<>( this::getLinearUnitValue) - .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2) - .build(); - + .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) + .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) + .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) + .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2) + .build(); + /** * A parser that can parse unit prefix expressions * @@ -1256,13 +1257,16 @@ public final class UnitDatabase { * @since v0.2.0 */ private final ExpressionParser<UnitPrefix> prefixExpressionParser = new ExpressionParser.Builder<>( - this::getPrefix).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) - .addBinaryOperator("^", - (o1, o2) -> o1.toExponent(o2.getMultiplier()), 1) - .build(); - + this::getPrefix).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) + .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) + .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) + .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), + 2) + .build(); + /** * A parser that can parse unit dimension expressions. * @@ -1271,9 +1275,14 @@ public final class UnitDatabase { */ private final ExpressionParser<ObjectProduct<BaseDimension>> unitDimensionParser = new ExpressionParser.Builder<>( this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build(); - + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2) + .addNumericOperator("^", (o1, o2) -> { + int exponent = (int) Math.round(o2.value()); + return o1.toExponent(exponent); + }, 1).build(); + /** * Creates the {@code UnitsDatabase}. * @@ -1283,7 +1292,7 @@ public final class UnitDatabase { public UnitDatabase() { this(prefixes -> true); } - + /** * Creates the {@code UnitsDatabase} * @@ -1301,7 +1310,7 @@ public final class UnitDatabase { entry -> this.prefixRepetitionRule .test(this.getPrefixesFromName(entry.getKey()))); } - + /** * Adds a unit dimension to the database. * @@ -1319,7 +1328,7 @@ public final class UnitDatabase { .withName(dimension.getNameSymbol().withExtraName(name)); this.dimensions.put(name, namedDimension); } - + /** * Adds to the list from a line in a unit dimension file. * @@ -1338,7 +1347,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1347,12 +1356,12 @@ public final class UnitDatabase { lineCounter)); final String name = lineMatcher.group(1); final String expression = lineMatcher.group(2); - + // if (name.endsWith(" ")) { // System.err.printf("Warning - line %d's dimension name ends in a space", // lineCounter); // } - + // if expression is "!", search for an existing dimension // if no unit found, throw an error if (expression.equals("!")) { @@ -1368,11 +1377,11 @@ public final class UnitDatabase { System.err.printf("Parsing error on line %d:%n", lineCounter); throw e; } - + this.addDimension(name, dimension); } } - + /** * Adds a unit prefix to the database. * @@ -1389,7 +1398,7 @@ public final class UnitDatabase { this.prefixes.put(Objects.requireNonNull(name, "name must not be null."), namedPrefix); } - + /** * Adds a unit to the database. * @@ -1406,7 +1415,7 @@ public final class UnitDatabase { this.prefixlessUnits.put( Objects.requireNonNull(name, "name must not be null."), namedUnit); } - + /** * Adds to the list from a line in a unit file. * @@ -1425,7 +1434,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1433,15 +1442,15 @@ public final class UnitDatabase { "Error at line %d: Lines of a unit file must consist of a unit name, then spaces or tabs, then a unit expression.", lineCounter)); final String name = lineMatcher.group(1); - + final String expression = lineMatcher.group(2); - + // this code should never occur // if (name.endsWith(" ")) { // System.err.printf("Warning - line %d's unit name ends in a space", // lineCounter); // } - + // if expression is "!", search for an existing unit // if no unit found, throw an error if (expression.equals("!")) { @@ -1474,7 +1483,7 @@ public final class UnitDatabase { } } } - + /** * Removes all units, prefixes and dimensions from this database. * @@ -1485,7 +1494,7 @@ public final class UnitDatabase { this.prefixes.clear(); this.prefixlessUnits.clear(); } - + /** * Tests if the database has a unit dimension with this name. * @@ -1497,7 +1506,7 @@ public final class UnitDatabase { public boolean containsDimensionName(final String name) { return this.dimensions.containsKey(name); } - + /** * Tests if the database has a unit prefix with this name. * @@ -1509,7 +1518,7 @@ public final class UnitDatabase { public boolean containsPrefixName(final String name) { return this.prefixes.containsKey(name); } - + /** * Tests if the database has a unit with this name, taking prefixes into * consideration @@ -1522,7 +1531,7 @@ public final class UnitDatabase { public boolean containsUnitName(final String name) { return this.units.containsKey(name); } - + /** * @return a map mapping dimension names to dimensions * @since 2019-04-13 @@ -1531,7 +1540,7 @@ public final class UnitDatabase { public Map<String, ObjectProduct<BaseDimension>> dimensionMap() { return Collections.unmodifiableMap(this.dimensions); } - + /** * Evaluates a unit expression, following the same rules as * {@link #getUnitFromExpression}. @@ -1542,46 +1551,42 @@ public final class UnitDatabase { */ public LinearUnitValue evaluateUnitExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a unit as an alias, or a number with precision first if (this.containsUnitName(expression)) return this.getLinearUnitValue(expression); - + // force operators to have spaces String modifiedExpression = expression; modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ "); modifiedExpression = modifiedExpression.replaceAll("-", " - "); - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + // the previous operation breaks negative numbers, fix them! // (i.e. -2 becomes - 2) // FIXME the previous operaton also breaks stuff like "1e-5" for (int i = 0; i < modifiedExpression.length(); i++) { if (modifiedExpression.charAt(i) == '-' - && (i < 2 || Arrays.asList('+', '-', '*', '/', '^') + && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^') .contains(modifiedExpression.charAt(i - 2)))) { // found a broken negative number modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2); } } - + return this.unitValueExpressionParser.parseExpression(modifiedExpression); } - + /** * Gets a unit dimension from the database using its name. * - * <p> - * This method accepts exponents, like "L^3" - * </p> - * * @param name dimension's name * @return dimension * @since 2019-03-14 @@ -1589,32 +1594,14 @@ public final class UnitDatabase { */ public ObjectProduct<BaseDimension> getDimension(final String name) { Objects.requireNonNull(name, "name must not be null."); - if (name.contains("^")) { - final String[] baseAndExponent = name.split("\\^"); - - final ObjectProduct<BaseDimension> base = this - .getDimension(baseAndExponent[0]); - - final int exponent; - try { - exponent = Integer - .parseInt(baseAndExponent[baseAndExponent.length - 1]); - } catch (final NumberFormatException e2) { - throw new IllegalArgumentException("Exponent must be an integer."); - } - - return base.toExponent(exponent); - } else { - final ObjectProduct<BaseDimension> dimension = this.dimensions - .get(name); - if (dimension == null) - throw new NoSuchElementException( - "No dimension with name \"" + name + "\"."); - else - return dimension; - } + final ObjectProduct<BaseDimension> dimension = this.dimensions.get(name); + if (dimension == null) + throw new NoSuchElementException( + "No dimension with name \"" + name + "\"."); + else + return dimension; } - + /** * Uses the database's data to parse an expression into a unit dimension * <p> @@ -1637,25 +1624,24 @@ public final class UnitDatabase { public ObjectProduct<BaseDimension> getDimensionFromExpression( final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a dimension as an alias first if (this.containsDimensionName(expression)) return this.getDimension(expression); - + // force operators to have spaces String modifiedExpression = expression; - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^"); - + return this.unitDimensionParser.parseExpression(modifiedExpression); } - + /** * Gets a unit. If it is linear, cast it to a LinearUnit and return it. * Otherwise, throw an {@code IllegalArgumentException}. @@ -1674,7 +1660,7 @@ public final class UnitDatabase { if (parts.size() != 2) throw new IllegalArgumentException( "Format nonlinear units like: unit(value)."); - + // solve the function final Unit unit = this.getUnit(parts.get(0)); final double value = Double.parseDouble( @@ -1683,7 +1669,7 @@ public final class UnitDatabase { } else { // get a linear unit final Unit unit = this.getUnit(name); - + if (unit instanceof LinearUnit) return (LinearUnit) unit; else @@ -1691,7 +1677,7 @@ public final class UnitDatabase { String.format("%s is not a linear unit.", name)); } } - + /** * Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be * converted to their base units. @@ -1709,7 +1695,7 @@ public final class UnitDatabase { return LinearUnitValue.getExact(this.getLinearUnit(name), 1); } } - + /** * Gets a unit prefix from the database from its name * @@ -1730,7 +1716,7 @@ public final class UnitDatabase { return prefix; } } - + /** * Gets all of the prefixes that are on a unit name, in application order. * @@ -1741,12 +1727,12 @@ public final class UnitDatabase { List<UnitPrefix> getPrefixesFromName(final String unitName) { final List<UnitPrefix> prefixes = new ArrayList<>(); String name = unitName; - + while (!this.prefixlessUnits.containsKey(name)) { // find the longest prefix String longestPrefixName = null; int longestLength = name.length(); - + while (longestPrefixName == null) { longestLength--; if (longestLength <= 0) @@ -1756,7 +1742,7 @@ public final class UnitDatabase { longestPrefixName = name.substring(0, longestLength); } } - + // longest prefix found! final UnitPrefix prefix = this.getPrefix(longestPrefixName); prefixes.add(0, prefix); @@ -1764,7 +1750,7 @@ public final class UnitDatabase { } return prefixes; } - + /** * Gets a unit prefix from a prefix expression * <p> @@ -1781,24 +1767,24 @@ public final class UnitDatabase { */ public UnitPrefix getPrefixFromExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a unit as an alias first if (this.containsUnitName(expression)) return this.getPrefix(expression); - + // force operators to have spaces String modifiedExpression = expression; - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + return this.prefixExpressionParser.parseExpression(modifiedExpression); } - + /** * @return the prefixRepetitionRule * @since 2020-08-26 @@ -1806,7 +1792,7 @@ public final class UnitDatabase { public final Predicate<List<UnitPrefix>> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * Gets a unit from the database from its name, looking for prefixes. * @@ -1839,9 +1825,9 @@ public final class UnitDatabase { } else return unit; } - + } - + /** * Uses the database's unit data to parse an expression into a unit * <p> @@ -1865,38 +1851,38 @@ public final class UnitDatabase { */ public Unit getUnitFromExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a unit as an alias first if (this.containsUnitName(expression)) return this.getUnit(expression); - + // force operators to have spaces String modifiedExpression = expression; modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ "); modifiedExpression = modifiedExpression.replaceAll("-", " - "); - + // format expression for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + // the previous operation breaks negative numbers, fix them! // (i.e. -2 becomes - 2) for (int i = 0; i < modifiedExpression.length(); i++) { if (modifiedExpression.charAt(i) == '-' - && (i < 2 || Arrays.asList('+', '-', '*', '/', '^') + && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^') .contains(modifiedExpression.charAt(i - 2)))) { // found a broken negative number modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2); } } - + return this.unitExpressionParser.parseExpression(modifiedExpression); } - + /** * Adds all dimensions from a file, using data from the database to parse * them. @@ -1936,7 +1922,7 @@ public final class UnitDatabase { throw new IllegalArgumentException("Could not read file " + file, e); } } - + /** * Adds all dimensions from a {@code InputStream}. Otherwise, works like * {@link #loadDimensionFile}. @@ -1952,7 +1938,7 @@ public final class UnitDatabase { } } } - + /** * Adds all units from a file, using data from the database to parse them. * <p> @@ -1991,7 +1977,7 @@ public final class UnitDatabase { throw new IllegalArgumentException("Could not read file " + file, e); } } - + /** * Adds all units from a {@code InputStream}. Otherwise, works like * {@link #loadUnitsFile}. @@ -2007,7 +1993,7 @@ public final class UnitDatabase { } } } - + /** * @param includeDuplicates if false, duplicates are removed from the map * @return a map mapping prefix names to prefixes @@ -2022,7 +2008,7 @@ public final class UnitDatabase { .conditionalExistenceMap(this.prefixes, entry -> !isRemovableDuplicate(this.prefixes, entry))); } - + /** * @param prefixRepetitionRule the prefixRepetitionRule to set * @since 2020-08-26 @@ -2031,7 +2017,7 @@ public final class UnitDatabase { Predicate<List<UnitPrefix>> prefixRepetitionRule) { this.prefixRepetitionRule = prefixRepetitionRule; } - + /** * @return a string stating the number of units, prefixes and dimensions in * the database @@ -2043,7 +2029,7 @@ public final class UnitDatabase { this.prefixlessUnits.size(), this.prefixes.size(), this.dimensions.size()); } - + /** * Returns a map mapping unit names to units, including units with prefixes. * <p> @@ -2075,7 +2061,7 @@ public final class UnitDatabase { return this.units; // PrefixedUnitMap is immutable so I don't need to make // an unmodifiable map. } - + /** * @param includeDuplicates if true, duplicate units will all exist in the * map; if false, only one of each unit will exist, diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index e1f7788..9035969 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -40,7 +40,7 @@ public final class UnitPrefix implements Nameable { public static UnitPrefix valueOf(final double multiplier) { return new UnitPrefix(multiplier, NameSymbol.EMPTY); } - + /** * Gets a {@code UnitPrefix} from a multiplier and a name * @@ -55,21 +55,21 @@ public final class UnitPrefix implements Nameable { return new UnitPrefix(multiplier, Objects.requireNonNull(ns, "ns must not be null.")); } - + /** * This prefix's name(s) and symbol. * * @since 2022-04-16 */ private final NameSymbol nameSymbol; - + /** * The number that this prefix multiplies units by * * @since 2019-10-16 */ private final double multiplier; - + /** * Creates the {@code DefaultUnitPrefix}. * @@ -81,7 +81,7 @@ public final class UnitPrefix implements Nameable { this.multiplier = multiplier; this.nameSymbol = ns; } - + /** * Divides this prefix by a scalar * @@ -92,7 +92,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix dividedBy(final double divisor) { return valueOf(this.getMultiplier() / divisor); } - + /** * Divides this prefix by {@code other}. * @@ -104,7 +104,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix dividedBy(final UnitPrefix other) { return valueOf(this.getMultiplier() / other.getMultiplier()); } - + /** * {@inheritDoc} * @@ -122,7 +122,7 @@ public final class UnitPrefix implements Nameable { return DecimalComparison.equals(this.getMultiplier(), other.getMultiplier()); } - + /** * @return prefix's multiplier * @since 2019-11-26 @@ -130,12 +130,12 @@ public final class UnitPrefix implements Nameable { public double getMultiplier() { return this.multiplier; } - + @Override public NameSymbol getNameSymbol() { return this.nameSymbol; } - + /** * {@inheritDoc} * @@ -145,7 +145,7 @@ public final class UnitPrefix implements Nameable { public int hashCode() { return DecimalComparison.hash(this.getMultiplier()); } - + /** * Multiplies this prefix by a scalar * @@ -156,7 +156,25 @@ public final class UnitPrefix implements Nameable { public UnitPrefix times(final double multiplicand) { return valueOf(this.getMultiplier() * multiplicand); } - + + /** + * Adds {@code other} to this prefix and returns the result. + * + * @since 2024-03-03 + */ + public UnitPrefix plus(final UnitPrefix other) { + return valueOf(this.getMultiplier() + other.getMultiplier()); + } + + /** + * Subtracts {@code other} from this prefix and returns the result. + * + * @since 2024-03-03 + */ + public UnitPrefix minus(final UnitPrefix other) { + return valueOf(this.getMultiplier() - other.getMultiplier()); + } + /** * Multiplies this prefix by {@code other}. * @@ -168,7 +186,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix times(final UnitPrefix other) { return valueOf(this.getMultiplier() * other.getMultiplier()); } - + /** * Raises this prefix to an exponent. * @@ -180,7 +198,7 @@ public final class UnitPrefix implements Nameable { public UnitPrefix toExponent(final double exponent) { return valueOf(Math.pow(this.getMultiplier(), exponent)); } - + /** * @return a string describing the prefix and its multiplier */ @@ -195,7 +213,7 @@ public final class UnitPrefix implements Nameable { else return String.format("Unit Prefix (\u00D7 %s)", this.multiplier); } - + /** * @param ns name(s) and symbol to use * @return copy of this prefix with provided name(s) and symbol diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java index 7cebf2d..9a87288 100644 --- a/src/main/java/sevenUnits/unit/UnitType.java +++ b/src/main/java/sevenUnits/unit/UnitType.java @@ -33,7 +33,7 @@ import java.util.function.Predicate; */ public enum UnitType { METRIC, SEMI_METRIC, NON_METRIC; - + /** * Determines which type a unit is. The type will be: * <ul> diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index 339263d..2d01831 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -42,10 +42,10 @@ public final class UnitValue { return new UnitValue( Objects.requireNonNull(unit, "unit must not be null"), value); } - + private final Unit unit; private final double value; - + /** * @param unit the unit being used * @param value the value being represented @@ -54,7 +54,7 @@ public final class UnitValue { this.unit = unit; this.value = value; } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -62,7 +62,7 @@ public final class UnitValue { public final boolean canConvertTo(Unit other) { return this.unit.canConvertTo(other); } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -70,7 +70,7 @@ public final class UnitValue { public final <W> boolean canConvertTo(Unitlike<W> other) { return this.unit.canConvertTo(other); } - + /** * Returns a UnitlikeValue that represents the same value expressed in a * different unitlike form. @@ -83,7 +83,7 @@ public final class UnitValue { return UnitlikeValue.of(other, this.unit.convertTo(other, this.getValue())); } - + /** * Returns a UnitValue that represents the same value expressed in a * different unit @@ -95,7 +95,7 @@ public final class UnitValue { return UnitValue.of(other, this.getUnit().convertTo(other, this.getValue())); } - + /** * Returns this unit value represented as a {@code LinearUnitValue} with this * unit's base unit as the base. @@ -108,7 +108,7 @@ public final class UnitValue { final LinearUnit base = LinearUnit.getBase(this.unit).withName(ns); return this.convertToLinear(base); } - + /** * @return a {@code LinearUnitValue} that is equivalent to this value. It * will have zero uncertainty. @@ -118,7 +118,7 @@ public final class UnitValue { return LinearUnitValue.getExact(other, this.getUnit().convertTo(other, this.getValue())); } - + /** * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 @@ -135,7 +135,7 @@ public final class UnitValue { .doubleToLongBits( other.getUnit().convertToBase(other.getValue())); } - + /** * @return the unit * @since 2020-09-29 @@ -143,7 +143,7 @@ public final class UnitValue { public final Unit getUnit() { return this.unit; } - + /** * @return the value * @since 2020-09-29 @@ -151,13 +151,13 @@ public final class UnitValue { public final double getValue() { return this.value; } - + @Override public int hashCode() { return Objects.hash(this.getUnit().getBase(), this.getUnit().convertFromBase(this.getValue())); } - + @Override public String toString() { final Optional<String> primaryName = this.getUnit().getPrimaryName(); diff --git a/src/main/java/sevenUnits/unit/Unitlike.java b/src/main/java/sevenUnits/unit/Unitlike.java index 68de2c2..fef424e 100644 --- a/src/main/java/sevenUnits/unit/Unitlike.java +++ b/src/main/java/sevenUnits/unit/Unitlike.java @@ -55,7 +55,7 @@ public abstract class Unitlike<V> implements Nameable { return new FunctionalUnitlike<>(base, NameSymbol.EMPTY, converterFrom, converterTo); } - + /** * Returns a unitlike form from its base and the functions it uses to convert * to and from its base. @@ -78,28 +78,28 @@ public abstract class Unitlike<V> implements Nameable { final ToDoubleFunction<W> converterTo, final NameSymbol ns) { return new FunctionalUnitlike<>(base, ns, converterFrom, converterTo); } - + /** * The combination of units that this unit is based on. * * @since 2019-10-16 */ private final ObjectProduct<BaseUnit> unitBase; - + /** * This unit's name(s) and symbol * * @since 2020-09-07 */ private final NameSymbol nameSymbol; - + /** * Cache storing the result of getDimension() * * @since 2019-10-16 */ private transient ObjectProduct<BaseDimension> dimension = null; - + /** * @param unitBase * @since 2020-09-07 @@ -109,7 +109,7 @@ public abstract class Unitlike<V> implements Nameable { "unitBase may not be null"); this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); } - + /** * Checks if a value expressed in this unitlike form can be converted to a * value expressed in {@code other} @@ -124,7 +124,7 @@ public abstract class Unitlike<V> implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + /** * Checks if a value expressed in this unitlike form can be converted to a * value expressed in {@code other} @@ -139,9 +139,9 @@ public abstract class Unitlike<V> implements Nameable { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } - + protected abstract V convertFromBase(double value); - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -150,7 +150,7 @@ public abstract class Unitlike<V> implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -168,7 +168,7 @@ public abstract class Unitlike<V> implements Nameable { throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } - + /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. @@ -196,9 +196,9 @@ public abstract class Unitlike<V> implements Nameable { throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } - + protected abstract double convertToBase(V value); - + /** * @return combination of units that this unit is based on * @since 2018-12-22 @@ -207,7 +207,7 @@ public abstract class Unitlike<V> implements Nameable { public final ObjectProduct<BaseUnit> getBase() { return this.unitBase; } - + /** * @return dimension measured by this unit * @since 2018-12-22 @@ -217,16 +217,16 @@ public abstract class Unitlike<V> implements Nameable { if (this.dimension == null) { final Map<BaseUnit, Integer> mapping = this.unitBase.exponentMap(); final Map<BaseDimension, Integer> dimensionMap = new HashMap<>(); - + for (final BaseUnit key : mapping.keySet()) { dimensionMap.put(key.getBaseDimension(), mapping.get(key)); } - + this.dimension = ObjectProduct.fromExponentMapping(dimensionMap); } return this.dimension; } - + /** * @return the nameSymbol * @since 2020-09-07 @@ -235,7 +235,7 @@ public abstract class Unitlike<V> implements Nameable { public final NameSymbol getNameSymbol() { return this.nameSymbol; } - + @Override public String toString() { return this.getPrimaryName().orElse("Unnamed unitlike form") @@ -247,7 +247,7 @@ public abstract class Unitlike<V> implements Nameable { + (this.getOtherNames().isEmpty() ? "" : ", also called " + String.join(", ", this.getOtherNames())); } - + /** * @param ns name(s) and symbol to use * @return a copy of this unitlike form with provided name(s) and symbol diff --git a/src/main/java/sevenUnits/unit/UnitlikeValue.java b/src/main/java/sevenUnits/unit/UnitlikeValue.java index 26354b1..ad0d1ea 100644 --- a/src/main/java/sevenUnits/unit/UnitlikeValue.java +++ b/src/main/java/sevenUnits/unit/UnitlikeValue.java @@ -34,10 +34,10 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { V value) { return new UnitlikeValue<>(unitlike, value); } - + private final T unitlike; private final V value; - + /** * @param unitlike * @param value @@ -47,7 +47,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { this.unitlike = unitlike; this.value = value; } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -55,7 +55,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final boolean canConvertTo(Unit other) { return this.unitlike.canConvertTo(other); } - + /** * @return true if this value can be converted to {@code other}. * @since 2020-10-01 @@ -63,7 +63,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final <W> boolean canConvertTo(Unitlike<W> other) { return this.unitlike.canConvertTo(other); } - + /** * Returns a UnitlikeValue that represents the same value expressed in a * different unitlike form. @@ -76,7 +76,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return UnitlikeValue.of(other, this.unitlike.convertTo(other, this.getValue())); } - + /** * Returns a UnitValue that represents the same value expressed in a * different unit @@ -88,7 +88,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return UnitValue.of(other, this.unitlike.convertTo(other, this.getValue())); } - + /** * Returns this unit value represented as a {@code LinearUnitValue} with this * unit's base unit as the base. @@ -101,7 +101,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { final LinearUnit base = LinearUnit.getBase(this.unitlike).withName(ns); return this.convertToLinear(base); } - + /** * @return a {@code LinearUnitValue} that is equivalent to this value. It * will have zero uncertainty. @@ -111,7 +111,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return LinearUnitValue.getExact(other, this.getUnitlike().convertTo(other, this.getValue())); } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -131,7 +131,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { return false; return true; } - + /** * @return the unitlike * @since 2020-09-29 @@ -139,7 +139,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final Unitlike<V> getUnitlike() { return this.unitlike; } - + /** * @return the value * @since 2020-09-29 @@ -147,7 +147,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { public final V getValue() { return this.value; } - + @Override public int hashCode() { final int prime = 31; @@ -158,7 +158,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> { + (this.getValue() == null ? 0 : this.getValue().hashCode()); return result; } - + @Override public String toString() { final Optional<String> primaryName = this.getUnitlike().getPrimaryName(); diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index bee4dd1..b71a4e0 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -67,7 +67,7 @@ public final class ConditionalExistenceCollections { extends AbstractCollection<E> { final Collection<E> collection; final Predicate<E> existenceCondition; - + /** * Creates the {@code ConditionalExistenceCollection}. * @@ -80,38 +80,38 @@ public final class ConditionalExistenceCollections { 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 // therefore this cast will always work @SuppressWarnings("unchecked") final E e = (E) o; - + return this.existenceCondition.test(e); } - + @Override public Iterator<E> iterator() { 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 @@ -119,32 +119,32 @@ public final class ConditionalExistenceCollections { 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(); } - + @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> 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. * @@ -157,7 +157,7 @@ public final class ConditionalExistenceCollections { final Predicate<E> existenceCondition; E nextElement; boolean hasNext; - + /** * Creates the {@code ConditionalExistenceIterator}. * @@ -171,7 +171,7 @@ public final class ConditionalExistenceCollections { this.existenceCondition = condition; this.getAndSetNextElement(); } - + /** * Gets the next element, and sets nextElement and hasNext accordingly. * @@ -188,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()) { @@ -203,13 +203,13 @@ 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. * @@ -221,7 +221,7 @@ public final class ConditionalExistenceCollections { static final class ConditionalExistenceMap<K, V> extends AbstractMap<K, V> { Map<K, V> map; Predicate<Entry<K, V>> entryExistenceCondition; - + /** * Creates the {@code ConditionalExistenceMap}. * @@ -234,81 +234,81 @@ public final class ConditionalExistenceCollections { 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<K, V> entry = new SimpleEntry<>(keyAsK, value); return this.entryExistenceCondition.test(entry); } - + @Override public Set<Entry<K, V>> entrySet() { 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<K, V> 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<K> keySet() { return conditionalExistenceSet(this.map.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<K, V> 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<V> values() { // maybe change this to use ConditionalExistenceCollection return super.values(); } } - + /** * Elements in this set only exist if a certain condition is true. * @@ -319,7 +319,7 @@ public final class ConditionalExistenceCollections { static final class ConditionalExistenceSet<E> extends AbstractSet<E> { private final Set<E> set; private final Predicate<E> existenceCondition; - + /** * Creates the {@code ConditionalNonexistenceSet}. * @@ -332,7 +332,7 @@ public final class ConditionalExistenceCollections { this.set = set; this.existenceCondition = existenceCondition; } - + /** * {@inheritDoc} * <p> @@ -343,33 +343,33 @@ public final class ConditionalExistenceCollections { 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 // therefore this cast will always work @SuppressWarnings("unchecked") final E e = (E) o; - + return this.existenceCondition.test(e); } - + @Override public Iterator<E> iterator() { 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 @@ -377,31 +377,31 @@ public final class ConditionalExistenceCollections { 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> 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. @@ -418,7 +418,7 @@ public final class ConditionalExistenceCollections { return new ConditionalExistenceCollection<>(collection, existenceCondition); } - + /** * Elements in the returned wrapper iterator are ignored if they don't pass a * condition. @@ -433,7 +433,7 @@ public final class ConditionalExistenceCollections { final Iterator<E> iterator, final Predicate<E> existenceCondition) { return new ConditionalExistenceIterator<>(iterator, existenceCondition); } - + /** * Mappings in the returned wrapper map are ignored if the corresponding * entry doesn't pass a condition @@ -450,7 +450,7 @@ public final class ConditionalExistenceCollections { final Predicate<Entry<K, V>> entryExistenceCondition) { return new ConditionalExistenceMap<>(map, entryExistenceCondition); } - + /** * Elements in the returned wrapper set are ignored if they don't pass a * condition. diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index a5cbbaa..0515b6b 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -34,7 +34,7 @@ public final class DecimalComparison { * @since v0.2.0 */ public static final double DOUBLE_EPSILON = 1.0e-15; - + /** * The value used for float comparison. If two float values are within this * value multiplied by the larger value, they are considered equal. @@ -43,7 +43,7 @@ public final class DecimalComparison { * @since v0.2.0 */ public static final float FLOAT_EPSILON = 1.0e-6f; - + /** * Tests for equality of double values using {@link #DOUBLE_EPSILON}. * <p> @@ -74,7 +74,7 @@ public final class DecimalComparison { public static final boolean equals(final double a, final double b) { return DecimalComparison.equals(a, b, DOUBLE_EPSILON); } - + /** * Tests for double equality using a custom epsilon value. * @@ -106,7 +106,7 @@ public final class DecimalComparison { final double epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } - + /** * Tests for equality of float values using {@link #FLOAT_EPSILON}. * @@ -136,7 +136,7 @@ public final class DecimalComparison { public static final boolean equals(final float a, final float b) { return DecimalComparison.equals(a, b, FLOAT_EPSILON); } - + /** * Tests for float equality using a custom epsilon value. * @@ -168,7 +168,7 @@ public final class DecimalComparison { final float epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } - + /** * Tests for equality of {@code UncertainDouble} values using * {@link #DOUBLE_EPSILON}. @@ -201,7 +201,7 @@ public final class DecimalComparison { return DecimalComparison.equals(a.value(), b.value()) && DecimalComparison.equals(a.uncertainty(), b.uncertainty()); } - + /** * Tests for {@code UncertainDouble} equality using a custom epsilon value. * @@ -235,7 +235,7 @@ public final class DecimalComparison { && DecimalComparison.equals(a.uncertainty(), b.uncertainty(), epsilon); } - + /** * Takes the hash code of doubles. Values that are equal according to * {@link #equals(double, double)} will have the same hash code. @@ -247,10 +247,10 @@ public final class DecimalComparison { public static final int hash(final double d) { return Float.hashCode((float) d); } - + // You may NOT get any DecimalComparison instances private DecimalComparison() { throw new AssertionError(); } - + } diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 941c2a4..e248ff0 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2024 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 @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.UnaryOperator; @@ -55,7 +56,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Function<String, ? extends T> objectObtainer; - + /** * The function of the space as an operator (like 3 x y) * @@ -63,7 +64,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private String spaceFunction = null; - + /** * A map mapping operator strings to operator functions, for unary * operators. @@ -72,7 +73,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityUnaryOperator<T>> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. @@ -81,7 +82,14 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityBinaryOperator<T>> binaryOperators; - + + /** + * A map mapping operator strings to numeric functions. + * + * @since 2024-03-23 + */ + private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; + /** * Creates the {@code Builder}. * @@ -96,8 +104,9 @@ public final class ExpressionParser<T> { "objectObtainer must not be null."); this.unaryOperators = new HashMap<>(); this.binaryOperators = new HashMap<>(); + this.numericOperators = new HashMap<>(); } - + /** * Adds a binary operator to the builder. * @@ -115,7 +124,7 @@ public final class ExpressionParser<T> { final BinaryOperator<T> operator, final int priority) { Objects.requireNonNull(text, "text must not be null."); Objects.requireNonNull(operator, "operator must not be null."); - + // Unfortunately, I cannot use a lambda because the // PriorityBinaryOperator requires arguments. final PriorityBinaryOperator<T> priorityOperator = new PriorityBinaryOperator<>( @@ -124,12 +133,42 @@ public final class ExpressionParser<T> { public T apply(final T t, final T u) { return operator.apply(t, u); } - + }; this.binaryOperators.put(text, priorityOperator); return this; } - + + /** + * Adds a two-argument operator where the second operator is a number. + * This is used for operations like vector scaling and exponentation. + * + * @param text text used to reference the operator, like '^' + * @param operator operator to add + * @param priority operator's priority, which determines which operators + * are applied first + * @return this builder + */ + public Builder<T> addNumericOperator(final String text, + final BiFunction<T, UncertainDouble, T> operator, + final int priority) { + Objects.requireNonNull(text, "text must not be null."); + Objects.requireNonNull(operator, "operator must not be null."); + + // Unfortunately, I cannot use a lambda because the + // PriorityBinaryOperator requires arguments. + final PriorityBiFunction<T, UncertainDouble, T> priorityOperator = new PriorityBiFunction<>( + priority) { + @Override + public T apply(final T t, final UncertainDouble u) { + return operator.apply(t, u); + } + + }; + this.numericOperators.put(text, priorityOperator); + return this; + } + /** * Adds a function for spaces. You must use the text of an existing binary * operator. @@ -141,15 +180,15 @@ public final class ExpressionParser<T> { */ public Builder<T> addSpaceFunction(final String operator) { Objects.requireNonNull(operator, "operator must not be null."); - + if (!this.binaryOperators.containsKey(operator)) throw new IllegalArgumentException(String .format("Could not find binary operator '%s'", operator)); - + this.spaceFunction = operator; return this; } - + /** * Adds a unary operator to the builder. * @@ -167,7 +206,7 @@ public final class ExpressionParser<T> { final UnaryOperator<T> operator, final int priority) { Objects.requireNonNull(text, "text must not be null."); Objects.requireNonNull(operator, "operator must not be null."); - + // Unfortunately, I cannot use a lambda because the // PriorityUnaryOperator requires arguments. final PriorityUnaryOperator<T> priorityOperator = new PriorityUnaryOperator<>( @@ -180,7 +219,7 @@ public final class ExpressionParser<T> { this.unaryOperators.put(text, priorityOperator); return this; } - + /** * @return an {@code ExpressionParser<T>} instance with the properties * given to this builder @@ -189,10 +228,10 @@ public final class ExpressionParser<T> { */ public ExpressionParser<T> build() { return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, - this.binaryOperators, this.spaceFunction); + this.binaryOperators, this.numericOperators, this.spaceFunction); } } - + /** * A binary operator with a priority field that determines which operators * apply first. @@ -212,7 +251,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final int priority; - + /** * Creates the {@code PriorityBinaryOperator}. * @@ -223,7 +262,7 @@ public final class ExpressionParser<T> { public PriorityBinaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. * @@ -243,7 +282,68 @@ public final class ExpressionParser<T> { else return 0; } - + + /** + * @return priority + * @since 2019-03-22 + * @since v0.2.0 + */ + public final int getPriority() { + return this.priority; + } + } + + /** + * A binary operator with a priority field that determines which operators + * apply first. + * + * @author Adrien Hopkins + * @param <T> type of operand and result + * @since 2019-03-17 + * @since v0.2.0 + */ + private static abstract class PriorityBiFunction<T, U, R> implements + BiFunction<T, U, R>, Comparable<PriorityBiFunction<T, U, R>> { + /** + * The operator's priority. Higher-priority operators are applied before + * lower-priority operators + * + * @since 2019-03-17 + * @since v0.2.0 + */ + private final int priority; + + /** + * Creates the {@code PriorityBinaryOperator}. + * + * @param priority operator's priority + * @since 2019-03-17 + * @since v0.2.0 + */ + public PriorityBiFunction(final int priority) { + this.priority = priority; + } + + /** + * Compares this object to another by priority. + * + * <p> + * {@inheritDoc} + * </p> + * + * @since 2019-03-17 + * @since v0.2.0 + */ + @Override + public int compareTo(final PriorityBiFunction<T, U, R> o) { + if (this.priority < o.priority) + return -1; + else if (this.priority > o.priority) + return 1; + else + return 0; + } + /** * @return priority * @since 2019-03-22 @@ -253,7 +353,7 @@ public final class ExpressionParser<T> { return this.priority; } } - + /** * A unary operator with a priority field that determines which operators * apply first. @@ -273,7 +373,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final int priority; - + /** * Creates the {@code PriorityUnaryOperator}. * @@ -284,7 +384,7 @@ public final class ExpressionParser<T> { public PriorityUnaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. * @@ -304,7 +404,7 @@ public final class ExpressionParser<T> { else return 0; } - + /** * @return priority * @since 2019-03-22 @@ -314,7 +414,7 @@ public final class ExpressionParser<T> { return this.priority; } } - + /** * The types of tokens that are available. * @@ -323,9 +423,9 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private static enum TokenType { - OBJECT, UNARY_OPERATOR, BINARY_OPERATOR; + OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR; } - + /** * The opening bracket. * @@ -333,7 +433,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ public static final char OPENING_BRACKET = '('; - + /** * The closing bracket. * @@ -341,7 +441,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ public static final char CLOSING_BRACKET = ')'; - + /** * Finds the other bracket in a pair of brackets, given the position of one. * @@ -355,9 +455,9 @@ public final class ExpressionParser<T> { private static int findBracketPair(final String string, final int bracketPosition) { Objects.requireNonNull(string, "string must not be null."); - + final char openingBracket = string.charAt(bracketPosition); - + // figure out what closing bracket to look for final char closingBracket; switch (openingBracket) { @@ -374,16 +474,16 @@ public final class ExpressionParser<T> { throw new IllegalArgumentException( String.format("Invalid bracket '%s'", openingBracket)); } - + // level of brackets. every opening bracket increments this; every closing // bracket decrements it int bracketLevel = 0; - + // iterate over the string to find the closing bracket for (int currentPosition = bracketPosition; currentPosition < string .length(); currentPosition++) { final char currentCharacter = string.charAt(currentPosition); - + if (currentCharacter == openingBracket) { bracketLevel++; } else if (currentCharacter == closingBracket) { @@ -392,10 +492,10 @@ public final class ExpressionParser<T> { return currentPosition; } } - + throw new IllegalArgumentException("No matching bracket found."); } - + /** * A function that obtains a parseable object from a string. For example, an * integer {@code ExpressionParser} would use {@code Integer::parseInt}. @@ -404,7 +504,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Function<String, ? extends T> objectObtainer; - + /** * A map mapping operator strings to operator functions, for unary operators. * @@ -412,7 +512,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityUnaryOperator<T>> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. @@ -421,7 +521,14 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityBinaryOperator<T>> binaryOperators; - + + /** + * A map mapping operator strings to numeric functions. + * + * @since 2024-03-23 + */ + private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; + /** * The operator for space, or null if spaces have no function. * @@ -429,27 +536,30 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final String spaceOperator; - + /** * Creates the {@code ExpressionParser}. * - * @param objectObtainer function to get objects from strings - * @param unaryOperators unary operators available to the parser - * @param binaryOperators binary operators available to the parser - * @param spaceOperator operator used by spaces + * @param objectObtainer function to get objects from strings + * @param unaryOperators unary operators available to the parser + * @param binaryOperators binary operators available to the parser + * @param numericOperators numeric operators available to the parser + * @param spaceOperator operator used by spaces * @since 2019-03-14 * @since v0.2.0 */ private ExpressionParser(final Function<String, ? extends T> objectObtainer, final Map<String, PriorityUnaryOperator<T>> unaryOperators, final Map<String, PriorityBinaryOperator<T>> binaryOperators, + final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators, final String spaceOperator) { this.objectObtainer = objectObtainer; this.unaryOperators = unaryOperators; this.binaryOperators = binaryOperators; + this.numericOperators = numericOperators; this.spaceOperator = spaceOperator; } - + /** * Converts a given mathematical expression to reverse Polish notation * (operators after operands). @@ -468,19 +578,19 @@ public final class ExpressionParser<T> { */ String convertExpressionToReversePolish(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final List<String> components = new ArrayList<>(); - + // the part of the expression remaining to parse String partialExpression = expression; - + // find and deal with brackets while (partialExpression.indexOf(OPENING_BRACKET) != -1) { final int openingBracketPosition = partialExpression .indexOf(OPENING_BRACKET); final int closingBracketPosition = findBracketPair(partialExpression, openingBracketPosition); - + // check for function if (openingBracketPosition > 0 && partialExpression.charAt(openingBracketPosition - 1) != ' ') { @@ -510,15 +620,15 @@ public final class ExpressionParser<T> { .substring(closingBracketPosition + 1); } } - + // add everything else components.addAll(Arrays.asList(partialExpression.split(" "))); - + // remove empty entries while (components.contains("")) { components.remove(""); } - + // deal with space multiplication (x y) if (this.spaceOperator != null) { for (int i = 0; i < components.size() - 1; i++) { @@ -528,7 +638,7 @@ public final class ExpressionParser<T> { } } } - + // turn the expression into reverse Polish while (true) { final int highestPriorityOperatorPosition = this @@ -536,7 +646,7 @@ public final class ExpressionParser<T> { if (highestPriorityOperatorPosition == -1) { break; } - + // swap components based on what kind of operator there is // 1 + 2 becomes 2 1 + // - 1 becomes 1 - @@ -554,6 +664,7 @@ public final class ExpressionParser<T> { operand + " " + unaryOperator); break; case BINARY_OPERATOR: + case NUMERIC_OPERATOR: if (components.size() < 3) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); @@ -570,11 +681,11 @@ public final class ExpressionParser<T> { throw new AssertionError("Expected operator, found non-operator."); } } - + // join all of the components together, then ensure there is only one // space in a row String expressionRPN = String.join(" ", components).replaceAll(" +", " "); - + while (expressionRPN.charAt(0) == ' ') { expressionRPN = expressionRPN.substring(1); } @@ -583,7 +694,7 @@ public final class ExpressionParser<T> { } return expressionRPN; } - + /** * Finds the position of the highest-priority operator in a list * @@ -601,18 +712,18 @@ public final class ExpressionParser<T> { // find highest priority int maxPriority = Integer.MIN_VALUE; int maxPriorityPosition = -1; - + // go over components one by one // if it is an operator, test its priority to see if it's max // if it is, update maxPriority and maxPriorityPosition for (int i = 0; i < components.size(); i++) { - + switch (this.getTokenType(components.get(i))) { case UNARY_OPERATOR: final PriorityUnaryOperator<T> unaryOperator = this.unaryOperators .get(components.get(i)); final int unaryPriority = unaryOperator.getPriority(); - + if (unaryPriority > maxPriority) { maxPriority = unaryPriority; maxPriorityPosition = i; @@ -622,21 +733,31 @@ public final class ExpressionParser<T> { final PriorityBinaryOperator<T> binaryOperator = this.binaryOperators .get(components.get(i)); final int binaryPriority = binaryOperator.getPriority(); - + if (binaryPriority > maxPriority) { maxPriority = binaryPriority; maxPriorityPosition = i; } break; + case NUMERIC_OPERATOR: + final PriorityBiFunction<T, UncertainDouble, T> numericOperator = this.numericOperators + .get(components.get(i)); + final int numericPriority = numericOperator.getPriority(); + + if (numericPriority > maxPriority) { + maxPriority = numericPriority; + maxPriorityPosition = i; + } + break; default: break; } } - + // max priority position found return maxPriorityPosition; } - + /** * Determines whether an inputted string is an object or an operator * @@ -648,15 +769,17 @@ public final class ExpressionParser<T> { */ private TokenType getTokenType(final String token) { Objects.requireNonNull(token, "token must not be null."); - + if (this.unaryOperators.containsKey(token)) return TokenType.UNARY_OPERATOR; else if (this.binaryOperators.containsKey(token)) return TokenType.BINARY_OPERATOR; + else if (this.numericOperators.containsKey(token)) + return TokenType.NUMERIC_OPERATOR; else return TokenType.OBJECT; } - + /** * Parses an expression. * @@ -670,7 +793,7 @@ public final class ExpressionParser<T> { return this.parseReversePolishExpression( this.convertExpressionToReversePolish(expression)); } - + /** * Parses an expression expressed in reverse Polish notation. * @@ -682,55 +805,86 @@ public final class ExpressionParser<T> { */ T parseReversePolishExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final Deque<T> stack = new ArrayDeque<>(); - + final Deque<UncertainDouble> doubleStack = new ArrayDeque<>(); + // iterate over every item in the expression, then for (final String item : expression.split(" ")) { // choose a path based on what kind of thing was just read switch (this.getTokenType(item)) { - + case BINARY_OPERATOR: if (stack.size() < 2) throw new IllegalStateException(String.format( "Attempted to call binary operator %s with only %d arguments.", item, stack.size())); - + // get two arguments and operator, then apply! final T o1 = stack.pop(); final T o2 = stack.pop(); final BinaryOperator<T> binaryOperator = this.binaryOperators .get(item); - + stack.push(binaryOperator.apply(o1, o2)); break; - + + case NUMERIC_OPERATOR: + if (stack.size() < 1 || doubleStack.size() < 1) + throw new IllegalStateException(String.format( + "Attempted to call binary operator %s with insufficient arguments.", + item)); + + final T ot = stack.pop(); + final UncertainDouble on = doubleStack.pop(); + final BiFunction<T, UncertainDouble, T> op = this.numericOperators + .get(item); + stack.push(op.apply(ot, on)); + break; + case OBJECT: // just add it to the stack - stack.push(this.objectObtainer.apply(item)); + // these try-catch statements are necessary + // to make the code as generalizable as possible + // also they're required for number formatting code because + // that's the only way to tell if an expression is a number or not. + try { + stack.push(this.objectObtainer.apply(item)); + } catch (Exception e) { + try { + doubleStack.push(UncertainDouble.fromString(item)); + } catch (IllegalArgumentException e2) { + try { + doubleStack.push( + UncertainDouble.of(Double.parseDouble(item), 0)); + } catch (NumberFormatException e3) { + throw e; + } + } + } break; - + case UNARY_OPERATOR: if (stack.size() < 1) throw new IllegalStateException(String.format( "Attempted to call unary operator %s with only %d arguments.", item, stack.size())); - + // get one argument and operator, then apply! final T o = stack.pop(); final UnaryOperator<T> unaryOperator = this.unaryOperators .get(item); - + stack.push(unaryOperator.apply(o)); break; default: throw new AssertionError( String.format("Internal error: Invalid token type %s.", this.getTokenType(item))); - + } } - + // return answer, or throw an exception if I can't if (stack.size() > 1) throw new IllegalStateException( diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 9388f63..49c44fa 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -33,7 +33,7 @@ import java.util.Set; public final class NameSymbol { public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), Optional.empty(), new HashSet<>()); - + /** * Creates a {@code NameSymbol}, ensuring that if primaryName is null and * otherNames is not empty, one name is moved from otherNames to primaryName @@ -43,7 +43,7 @@ public final class NameSymbol { private static final NameSymbol create(final String name, final String symbol, final Set<String> otherNames) { final Optional<String> primaryName; - + if (name == null && !otherNames.isEmpty()) { // get primary name and remove it from savedNames final Iterator<String> it = otherNames.iterator(); @@ -53,11 +53,11 @@ public final class NameSymbol { } else { primaryName = Optional.ofNullable(name); } - + return new NameSymbol(primaryName, Optional.ofNullable(symbol), otherNames); } - + /** * Gets a {@code NameSymbol} with a primary name, a symbol and no other * names. @@ -72,7 +72,7 @@ public final class NameSymbol { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>()); } - + /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -90,7 +90,7 @@ public final class NameSymbol { new HashSet<>(Objects.requireNonNull(otherNames, "otherNames must not be null."))); } - + /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -108,7 +108,7 @@ public final class NameSymbol { new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, "otherNames must not be null.")))); } - + /** * Gets a {@code NameSymbol} with a primary name, no symbol, and no other * names. @@ -122,7 +122,7 @@ public final class NameSymbol { return new NameSymbol(Optional.of(name), Optional.empty(), new HashSet<>()); } - + /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -145,7 +145,7 @@ public final class NameSymbol { return NameSymbol.create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); } - + /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. @@ -168,7 +168,7 @@ public final class NameSymbol { return create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(Arrays.asList(otherNames))); } - + /** * Gets a {@code NameSymbol} with a symbol and no names. * @@ -181,12 +181,12 @@ public final class NameSymbol { return new NameSymbol(Optional.empty(), Optional.of(symbol), new HashSet<>()); } - + private final Optional<String> primaryName; private final Optional<String> symbol; - + private final Set<String> otherNames; - + /** * Creates the {@code NameSymbol}. * @@ -202,12 +202,12 @@ public final class NameSymbol { this.symbol = symbol; otherNames.remove(null); this.otherNames = Collections.unmodifiableSet(otherNames); - + if (this.primaryName.isEmpty()) { assert this.otherNames.isEmpty(); } } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -232,7 +232,7 @@ public final class NameSymbol { return false; return true; } - + /** * @return otherNames * @since 2019-10-21 @@ -240,7 +240,7 @@ public final class NameSymbol { public final Set<String> getOtherNames() { return this.otherNames; } - + /** * @return primaryName * @since 2019-10-21 @@ -248,7 +248,7 @@ public final class NameSymbol { public final Optional<String> getPrimaryName() { return this.primaryName; } - + /** * @return symbol * @since 2019-10-21 @@ -256,7 +256,7 @@ public final class NameSymbol { public final Optional<String> getSymbol() { return this.symbol; } - + @Override public int hashCode() { final int prime = 31; @@ -269,7 +269,7 @@ public final class NameSymbol { + (this.symbol == null ? 0 : this.symbol.hashCode()); return result; } - + /** * @return true iff this {@code NameSymbol} contains no names or symbols. */ @@ -277,7 +277,7 @@ public final class NameSymbol { // if primaryName is empty, otherNames must also be empty return this.primaryName.isEmpty() && this.symbol.isEmpty(); } - + @Override public String toString() { if (this.isEmpty()) @@ -288,7 +288,7 @@ public final class NameSymbol { else return this.primaryName.orElseGet(this.symbol::orElseThrow); } - + /** * Creates and returns a copy of this {@code NameSymbol} with the provided * extra name. If this {@code NameSymbol} has a primary name, the provided diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index e469d04..3959a64 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -35,14 +35,14 @@ public interface Nameable { final NameSymbol ns = this.getNameSymbol(); return ns.getPrimaryName().or(ns::getSymbol).orElse("Unnamed"); } - + /** * @return a {@code NameSymbol} that contains this object's primary name, * symbol and other names * @since 2020-09-07 */ NameSymbol getNameSymbol(); - + /** * @return set of alternate names * @since 2020-09-07 @@ -50,7 +50,7 @@ public interface Nameable { default Set<String> getOtherNames() { return this.getNameSymbol().getOtherNames(); } - + /** * @return preferred name of object * @since 2020-09-07 @@ -58,7 +58,7 @@ public interface Nameable { default Optional<String> getPrimaryName() { return this.getNameSymbol().getPrimaryName(); } - + /** * @return a short name for the object - if there's a symbol, it's that, * otherwise the symbol, otherwise "Unnamed" @@ -68,7 +68,7 @@ public interface Nameable { final NameSymbol ns = this.getNameSymbol(); return ns.getSymbol().or(ns::getPrimaryName).orElse("Unnamed"); } - + /** * @return short symbol representing object * @since 2020-09-07 diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 66bb773..5a29d79 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -44,7 +44,7 @@ public class ObjectProduct<T> implements Nameable { public static final <T> ObjectProduct<T> empty() { return new ObjectProduct<>(new HashMap<>()); } - + /** * Gets an {@code ObjectProduct} from an object-to-integer mapping * @@ -57,7 +57,7 @@ public class ObjectProduct<T> implements Nameable { final Map<T, Integer> map) { return new ObjectProduct<>(new HashMap<>(map)); } - + /** * Gets an ObjectProduct that has one of the inputted argument, and nothing * else. @@ -73,7 +73,7 @@ public class ObjectProduct<T> implements Nameable { map.put(object, 1); return new ObjectProduct<>(map); } - + /** * The objects that make up the product, mapped to their exponents. This map * treats zero as null, and is immutable. @@ -81,12 +81,12 @@ public class ObjectProduct<T> implements Nameable { * @since 2019-10-16 */ final Map<T, Integer> exponents; - + /** * The object's name and symbol */ private final NameSymbol nameSymbol; - + /** * Creates a {@code ObjectProduct} without a name/symbol. * @@ -96,7 +96,7 @@ public class ObjectProduct<T> implements Nameable { ObjectProduct(final Map<T, Integer> exponents) { this(exponents, NameSymbol.EMPTY); } - + /** * Creates the {@code ObjectProduct}. * @@ -110,7 +110,7 @@ public class ObjectProduct<T> implements Nameable { e -> !Integer.valueOf(0).equals(e.getValue()))); this.nameSymbol = nameSymbol; } - + /** * Calculates the quotient of two products * @@ -125,17 +125,17 @@ public class ObjectProduct<T> implements Nameable { final Set<T> objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map<T, Integer> map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) - other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + // this method relies on the use of ZeroIsNullMap @Override public boolean equals(final Object obj) { @@ -146,7 +146,7 @@ public class ObjectProduct<T> implements Nameable { final ObjectProduct<?> other = (ObjectProduct<?>) obj; return Objects.equals(this.exponents, other.exponents); } - + /** * @return immutable map mapping objects to exponents * @since 2019-10-16 @@ -154,7 +154,7 @@ public class ObjectProduct<T> implements Nameable { public Map<T, Integer> exponentMap() { return this.exponents; } - + /** * @return a set of all of the base objects with non-zero exponents that make * up this dimension. @@ -163,7 +163,7 @@ public class ObjectProduct<T> implements Nameable { */ public final Set<T> getBaseSet() { final Set<T> dimensions = new HashSet<>(); - + // add all dimensions with a nonzero exponent - zero exponents shouldn't // be there in the first place for (final T dimension : this.exponents.keySet()) { @@ -171,10 +171,10 @@ public class ObjectProduct<T> implements Nameable { dimensions.add(dimension); } } - + return dimensions; } - + /** * Gets the exponent for a specific dimension. * @@ -186,17 +186,17 @@ public class ObjectProduct<T> implements Nameable { public int getExponent(final T dimension) { return this.exponents.getOrDefault(dimension, 0); } - + @Override public NameSymbol getNameSymbol() { return this.nameSymbol; } - + @Override public int hashCode() { return Objects.hash(this.exponents); } - + /** * @return true if this product is a single object, i.e. it has one exponent * of one and no other nonzero exponents @@ -214,7 +214,7 @@ public class ObjectProduct<T> implements Nameable { } return oneCount == 1 && !twoOrMore; } - + /** * Multiplies this product by another * @@ -229,17 +229,17 @@ public class ObjectProduct<T> implements Nameable { final Set<T> objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map<T, Integer> map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) + other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + /** * Returns this product, but to an exponent * @@ -254,7 +254,7 @@ public class ObjectProduct<T> implements Nameable { } return new ObjectProduct<>(map); } - + /** * Converts this product to a string using the objects' * {@link Object#toString()} method (or {@link Nameable#getShortName} if @@ -271,7 +271,7 @@ public class ObjectProduct<T> implements Nameable { .toString(o -> o instanceof Nameable ? ((Nameable) o).getShortName() : o.toString()); } - + /** * Converts this product to a string. The objects that make up this product * are represented by {@code objectToString} @@ -283,7 +283,7 @@ public class ObjectProduct<T> implements Nameable { public String toString(final Function<T, String> objectToString) { final List<String> positiveStringComponents = new ArrayList<>(); final List<String> negativeStringComponents = new ArrayList<>(); - + // for each base object that makes up this object, add it and its exponent for (final T object : this.getBaseSet()) { final int exponent = this.exponents.get(object); @@ -297,15 +297,15 @@ public class ObjectProduct<T> implements Nameable { objectToString.apply(object), -exponent)); } } - + final String positiveString = positiveStringComponents.isEmpty() ? "1" : String.join(" * ", positiveStringComponents); final String negativeString = negativeStringComponents.isEmpty() ? "" : " / " + String.join(" * ", negativeStringComponents); - + return positiveString + negativeString; } - + /** * @return named version of this {@code ObjectProduct}, using data from * {@code nameSymbol} diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index e80e16e..fc47baa 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -61,7 +61,7 @@ public final class SemanticVersionNumber private final int patch; private final List<String> preReleaseIdentifiers; private final List<String> buildMetadata; - + /** * Creates a builder which can be used to create a * {@code SemanticVersionNumber} @@ -79,7 +79,7 @@ public final class SemanticVersionNumber this.preReleaseIdentifiers = new ArrayList<>(); this.buildMetadata = new ArrayList<>(); } - + /** * @return version number created by this builder * @since v0.4.0 @@ -89,7 +89,7 @@ public final class SemanticVersionNumber return new SemanticVersionNumber(this.major, this.minor, this.patch, this.preReleaseIdentifiers, this.buildMetadata); } - + /** * Adds one or more build metadata identifiers * @@ -109,7 +109,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds one or more build metadata identifiers * @@ -129,7 +129,7 @@ public final class SemanticVersionNumber } return this; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -142,13 +142,13 @@ public final class SemanticVersionNumber && this.patch == other.patch && Objects.equals( this.preReleaseIdentifiers, other.preReleaseIdentifiers); } - + @Override public int hashCode() { return Objects.hash(this.buildMetadata, this.major, this.minor, this.patch, this.preReleaseIdentifiers); } - + /** * Adds one or more numeric identifiers to the version number * @@ -167,7 +167,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds one or more pre-release identifier(s) to the version number * @@ -187,7 +187,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds one or more pre-release identifier(s) to the version number * @@ -207,7 +207,7 @@ public final class SemanticVersionNumber } return this; } - + /** * Adds a string identifier and an integer identifer to pre-release data * @@ -229,13 +229,13 @@ public final class SemanticVersionNumber this.preReleaseIdentifiers.add(Integer.toString(identifier2)); return this; } - + @Override public String toString() { return "Semantic Version Builder: " + this.build().toString(); } } - + /** * An alternative comparison method for version numbers. This uses the * version's natural order, but the build metadata will be compared (using @@ -257,21 +257,21 @@ public final class SemanticVersionNumber return naturalComparison; }; }; - + /** The alphanumeric pattern all identifiers must follow */ private static final Pattern VALID_IDENTIFIER = Pattern .compile("[0-9A-Za-z-]+"); - + /** The numeric pattern which causes special behaviour */ private static final Pattern NUMERIC_IDENTIFER = Pattern.compile("[0-9]+"); - + /** The pattern for a version number */ private static final Pattern VERSION_NUMBER = Pattern .compile("(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)" // main // version + "(?:-([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?" // pre-release + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"); // build data - + /** * Creates a builder that can be used to create a version number * @@ -296,7 +296,7 @@ public final class SemanticVersionNumber "Patch version must be non-negative."); return new SemanticVersionNumber.Builder(major, minor, patch); } - + /** * Compares two lists of strings based on SemVer's precedence rules * @@ -311,24 +311,24 @@ public final class SemanticVersionNumber // test pre-release size final int aSize = a.size(); final int bSize = b.size(); - + // no identifiers is greater than any identifiers if (aSize != 0 && bSize == 0) return -1; else if (aSize == 0 && bSize != 0) return 1; - + // test identifiers one by one for (int i = 0; i < Math.min(aSize, bSize); i++) { final String aElement = a.get(i); final String bElement = b.get(i); - + if (NUMERIC_IDENTIFER.matcher(aElement).matches()) { if (NUMERIC_IDENTIFER.matcher(bElement).matches()) { // both are numbers, compare them final int aNumber = Integer.parseInt(aElement); final int bNumber = Integer.parseInt(bElement); - + if (aNumber < bNumber) return -1; else if (aNumber > bNumber) @@ -350,7 +350,7 @@ public final class SemanticVersionNumber } } } - + // we just tested the stuff that's in common, maybe someone has more if (aSize < bSize) return -1; @@ -359,7 +359,7 @@ public final class SemanticVersionNumber else return 0; } - + /** * Gets a version number from a string in the official format * @@ -377,12 +377,12 @@ public final class SemanticVersionNumber throw new IllegalArgumentException( String.format("Provided string \"%s\" is not a version number", versionString)); - + // main parts final int major = Integer.parseInt(m.group(1)); final int minor = Integer.parseInt(m.group(2)); final int patch = Integer.parseInt(m.group(3)); - + // pre release final List<String> preRelease; if (m.group(4) == null) { @@ -390,7 +390,7 @@ public final class SemanticVersionNumber } else { preRelease = Arrays.asList(m.group(4).split("\\.")); } - + // build metadata final List<String> buildMetadata; if (m.group(5) == null) { @@ -398,12 +398,12 @@ public final class SemanticVersionNumber } else { buildMetadata = Arrays.asList(m.group(5).split("\\.")); } - + // return number return new SemanticVersionNumber(major, minor, patch, preRelease, buildMetadata); } - + /** * Tests whether a string is a valid Semantic Version string * @@ -415,7 +415,7 @@ public final class SemanticVersionNumber public static final boolean isValidVersionString(String versionString) { return VERSION_NUMBER.matcher(versionString).matches(); } - + /** * Creates a simple pre-release version number of the form * MAJOR.MINOR.PATH-TYPE.NUMBER (e.g. 1.2.3-alpha.4). @@ -454,7 +454,7 @@ public final class SemanticVersionNumber List.of(preReleaseType, Integer.toString(preReleaseNumber)), List.of()); } - + /** * Creates a {@code SemanticVersionNumber} instance without pre-release * identifiers or build metadata. @@ -484,14 +484,14 @@ public final class SemanticVersionNumber return new SemanticVersionNumber(major, minor, patch, List.of(), List.of()); } - + // parts of the version number private final int major; private final int minor; private final int patch; private final List<String> preReleaseIdentifiers; private final List<String> buildMetadata; - + /** * Creates a version number * @@ -511,7 +511,7 @@ public final class SemanticVersionNumber this.preReleaseIdentifiers = preReleaseIdentifiers; this.buildMetadata = buildMetadata; } - + /** * @return build metadata (empty if there is none) * @since v0.4.0 @@ -520,7 +520,7 @@ public final class SemanticVersionNumber public List<String> buildMetadata() { return Collections.unmodifiableList(this.buildMetadata); } - + /** * Compares two version numbers according to the official Semantic Versioning * order. @@ -538,23 +538,23 @@ public final class SemanticVersionNumber return -1; else if (this.major > o.major) return 1; - + if (this.minor < o.minor) return -1; else if (this.minor > o.minor) return 1; - + if (this.patch < o.patch) return -1; else if (this.patch > o.patch) return 1; - + // now we just compare pre-release identifiers // (remember: build metadata is ignored) return SemanticVersionNumber.compareIdentifiers( this.preReleaseIdentifiers, o.preReleaseIdentifiers); } - + /** * Determines the compatibility of code written for this version to * {@code other}. More specifically: @@ -590,11 +590,11 @@ public final class SemanticVersionNumber */ public boolean compatibleWith(SemanticVersionNumber other) { Objects.requireNonNull(other, "other may not be null"); - + return this.compareTo(other) == 0 || this.major != 0 && this.major == other.major && this.compareTo(other) < 0; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -621,7 +621,7 @@ public final class SemanticVersionNumber return false; return true; } - + @Override public int hashCode() { final int prime = 31; @@ -635,7 +635,7 @@ public final class SemanticVersionNumber : this.preReleaseIdentifiers.hashCode()); return result; } - + /** * @return true iff this version is stable (major version > 0 and not a * pre-release) @@ -645,7 +645,7 @@ public final class SemanticVersionNumber public boolean isStable() { return this.major > 0 && this.preReleaseIdentifiers.isEmpty(); } - + /** * @return the MAJOR version number, incremented when you make backwards * incompatible API changes @@ -655,7 +655,7 @@ public final class SemanticVersionNumber public int majorVersion() { return this.major; } - + /** * @return the MINOR version number, incremented when you add backwards * compatible functionality @@ -665,7 +665,7 @@ public final class SemanticVersionNumber public int minorVersion() { return this.minor; } - + /** * @return the PATCH version number, incremented when you make backwards * compatible bug fixes @@ -675,7 +675,7 @@ public final class SemanticVersionNumber public int patchVersion() { return this.patch; } - + /** * @return identifiers describing this pre-release (empty if not a * pre-release) @@ -685,7 +685,7 @@ public final class SemanticVersionNumber public List<String> preReleaseIdentifiers() { return Collections.unmodifiableList(this.preReleaseIdentifiers); } - + /** * Converts a version number to a string using the official SemVer format. * The core of a version is MAJOR.MINOR.PATCH, without zero-padding. If diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index ac523b3..66d8103 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -23,7 +23,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * A double with an associated uncertainty value. For example, 3.2 ± 0.2. + * A double with an associated uncertainty value. For example, 3.2 � 0.2. * <p> * All methods in this class throw a NullPointerException if any of their * arguments is null. @@ -35,20 +35,20 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * The exact value 0 */ public static final UncertainDouble ZERO = UncertainDouble.of(0, 0); - + static final String NUMBER_REGEX = "(\\d+(?:[\\.,]\\d+))"; - + /** * A regular expression that can recognize toString forms */ static final Pattern TO_STRING = Pattern.compile(NUMBER_REGEX - // optional "± [number]" - + "(?:\\s*(?:±|\\+-)\\s*" + NUMBER_REGEX + ")?"); - + // optional "� [number]" + + "(?:\\s*(?:�|\\+-)\\s*" + NUMBER_REGEX + ")?"); + /** * Gets an UncertainDouble from a double string. The uncertainty of the * double will be one of the lowest decimal place of the number. For example, - * "12345.678" will become 12345.678 ± 0.001. + * "12345.678" will become 12345.678 � 0.001. * * @throws NumberFormatException if the argument is not a number * @@ -59,13 +59,13 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { final double uncertainty = Math.pow(10, -value.scale()); return UncertainDouble.of(value.doubleValue(), uncertainty); } - + /** * Parses a string in the form of {@link UncertainDouble#toString(boolean)} * and returns the corresponding {@code UncertainDouble} instance. * <p> * This method allows some alternative forms of the string representation, - * such as using "+-" instead of "±". + * such as using "+-" instead of "�". * * @param s string to parse * @return {@code UncertainDouble} instance @@ -75,11 +75,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public static final UncertainDouble fromString(String s) { Objects.requireNonNull(s, "s may not be null"); final Matcher matcher = TO_STRING.matcher(s); - + if (!matcher.matches()) throw new IllegalArgumentException( "Could not parse stirng \"" + s + "\"."); - + double value, uncertainty; try { value = Double.parseDouble(matcher.group(1)); @@ -87,7 +87,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { throw new IllegalArgumentException( "String " + s + " not in correct format."); } - + final String uncertaintyString = matcher.group(2); if (uncertaintyString == null) { uncertainty = 0; @@ -99,10 +99,10 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { "String " + s + " not in correct format."); } } - + return UncertainDouble.of(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and <b>absolute</b> * uncertainty. @@ -112,7 +112,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public static final UncertainDouble of(double value, double uncertainty) { return new UncertainDouble(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and <b>relative</b> * uncertainty. @@ -123,11 +123,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { double relativeUncertainty) { return new UncertainDouble(value, value * relativeUncertainty); } - + private final double value; - + private final double uncertainty; - + /** * @param value * @param uncertainty @@ -138,13 +138,13 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { // uncertainty should only ever be positive this.uncertainty = Math.abs(uncertainty); } - + /** * Compares this {@code UncertainDouble} with another * {@code UncertainDouble}. * <p> - * This method only compares the values, not the uncertainties. So 3.1 ± 0.5 - * is considered less than 3.2 ± 0.5, even though they are equivalent. + * This method only compares the values, not the uncertainties. So 3.1 � 0.5 + * is considered less than 3.2 � 0.5, even though they are equivalent. * <p> * <b>Note:</b> The natural ordering of this class is inconsistent with * equals. Specifically, if two {@code UncertainDouble} instances {@code a} @@ -156,7 +156,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final int compareTo(UncertainDouble o) { return Double.compare(this.value, o.value); } - + /** * Returns the quotient of {@code this} and {@code other}. * @@ -167,7 +167,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.ofRelative(this.value / other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); } - + /** * Returns the quotient of {@code this} and the exact value {@code other}. * @@ -176,7 +176,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble dividedByExact(double other) { return UncertainDouble.of(this.value / other, this.uncertainty / other); } - + @Override public final boolean equals(Object obj) { if (this == obj) @@ -190,7 +190,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return false; return true; } - + /** * @param other another {@code UncertainDouble} * @return true iff this and {@code other} are within each other's @@ -202,7 +202,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return Math.abs(this.value - other.value) <= Math.min(this.uncertainty, other.uncertainty); } - + /** * Gets the preferred scale for rounding a value for toString. * @@ -217,19 +217,19 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { // the value is rounded to the same number of decimal places as the // uncertainty. final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); - + // the scale that will give the uncertainty two decimal places final int twoDecimalPlacesScale = bigUncertainty.scale() - bigUncertainty.precision() + 2; final BigDecimal roundedUncertainty = bigUncertainty .setScale(twoDecimalPlacesScale, RoundingMode.HALF_EVEN); - + if (roundedUncertainty.unscaledValue().intValue() >= 20) return twoDecimalPlacesScale - 1; // one decimal place else return twoDecimalPlacesScale; } - + @Override public final int hashCode() { final int prime = 31; @@ -238,7 +238,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { result = prime * result + Double.hashCode(this.uncertainty); return result; } - + /** * @return true iff the value has no uncertainty * @@ -247,7 +247,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final boolean isExact() { return this.uncertainty == 0; } - + /** * Returns the difference of {@code this} and {@code other}. * @@ -258,7 +258,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.of(this.value - other.value, Math.hypot(this.uncertainty, other.uncertainty)); } - + /** * Returns the difference of {@code this} and the exact value {@code other}. * @@ -267,7 +267,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble minusExact(double other) { return UncertainDouble.of(this.value - other, this.uncertainty); } - + /** * Returns the sum of {@code this} and {@code other}. * @@ -278,7 +278,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.of(this.value + other.value, Math.hypot(this.uncertainty, other.uncertainty)); } - + /** * Returns the sum of {@code this} and the exact value {@code other}. * @@ -287,7 +287,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble plusExact(double other) { return UncertainDouble.of(this.value + other, this.uncertainty); } - + /** * @return relative uncertainty * @since 2020-09-07 @@ -295,7 +295,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final double relativeUncertainty() { return this.uncertainty / this.value; } - + /** * Returns the product of {@code this} and {@code other}. * @@ -306,7 +306,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.ofRelative(this.value * other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); } - + /** * Returns the product of {@code this} and the exact value {@code other}. * @@ -315,7 +315,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final UncertainDouble timesExact(double other) { return UncertainDouble.of(this.value * other, this.uncertainty * other); } - + /** * Returns the result of {@code this} raised to the exponent {@code other}. * @@ -323,15 +323,15 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { */ public final UncertainDouble toExponent(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); - + final double result = Math.pow(this.value, other.value); final double relativeUncertainty = Math.hypot( other.value * this.relativeUncertainty(), Math.log(this.value) * other.uncertainty); - + return UncertainDouble.ofRelative(result, relativeUncertainty); } - + /** * Returns the result of {@code this} raised the exact exponent * {@code other}. @@ -342,7 +342,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { return UncertainDouble.ofRelative(Math.pow(this.value, other), this.relativeUncertainty() * other); } - + /** * Returns a string representation of this {@code UncertainDouble}. * <p> @@ -354,8 +354,8 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * Examples: * * <pre> - * UncertainDouble.of(3.27, 0.22).toString() = "3.3 ± 0.2" - * UncertainDouble.of(3.27, 0.13).toString() = "3.27 ± 0.13" + * UncertainDouble.of(3.27, 0.22).toString() = "3.3 � 0.2" + * UncertainDouble.of(3.27, 0.13).toString() = "3.27 � 0.13" * UncertainDouble.of(-5.01, 0).toString() = "-5.01" * </pre> * @@ -365,12 +365,12 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final String toString() { return this.toString(!this.isExact(), RoundingMode.HALF_EVEN); } - + /** * Returns a string representation of this {@code UncertainDouble}. * <p> * If {@code showUncertainty} is true, the string will be of the form "VALUE - * ± UNCERTAINTY", and if it is false the string will be of the form "VALUE" + * � UNCERTAINTY", and if it is false the string will be of the form "VALUE" * <p> * VALUE represents a string representation of this {@code UncertainDouble}'s * value. If the uncertainty is non-zero, the string will be rounded to the @@ -385,11 +385,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { * * <pre> * UncertainDouble.of(3.27, 0.22).toString(false) = "3.3" - * UncertainDouble.of(3.27, 0.22).toString(true) = "3.3 ± 0.2" + * UncertainDouble.of(3.27, 0.22).toString(true) = "3.3 � 0.2" * UncertainDouble.of(3.27, 0.13).toString(false) = "3.27" - * UncertainDouble.of(3.27, 0.13).toString(true) = "3.27 ± 0.13" + * UncertainDouble.of(3.27, 0.13).toString(true) = "3.27 � 0.13" * UncertainDouble.of(-5.01, 0).toString(false) = "-5.01" - * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 ± 0.0" + * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 � 0.0" * </pre> * * @since 2020-09-07 @@ -397,31 +397,31 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final String toString(boolean showUncertainty, RoundingMode roundingMode) { String valueString, uncertaintyString; - + // generate the string representation of value and uncertainty if (this.isExact()) { uncertaintyString = "0.0"; valueString = Double.toString(this.value); - + } else { // round the value and uncertainty according to getDisplayScale() final BigDecimal bigValue = BigDecimal.valueOf(this.value); final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); - + final int displayScale = this.getDisplayScale(); final BigDecimal roundedUncertainty = bigUncertainty .setScale(displayScale, roundingMode); final BigDecimal roundedValue = bigValue.setScale(displayScale, roundingMode); - + valueString = roundedValue.toString(); uncertaintyString = roundedUncertainty.toString(); } - - // return "value" or "value ± uncertainty" depending on showUncertainty - return valueString + (showUncertainty ? " ± " + uncertaintyString : ""); + + // return "value" or "value � uncertainty" depending on showUncertainty + return valueString + (showUncertainty ? " � " + uncertaintyString : ""); } - + /** * @return absolute uncertainty * @since 2020-09-07 @@ -429,7 +429,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> { public final double uncertainty() { return this.uncertainty; } - + /** * @return value without uncertainty * @since 2020-09-07 diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java index b56356d..1fb2709 100644 --- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java +++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java @@ -45,16 +45,16 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> { } 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 (!Metric.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 @@ -62,7 +62,7 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> { 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 @@ -79,7 +79,7 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> { // deka/hecto must be the last prefix, so this is always invalid return false; } - + // set part if (Metric.YOTTA.equals(prefix) || Metric.YOCTO.equals(prefix)) { part = 0; diff --git a/src/main/java/sevenUnitsGUI/DelegateListModel.java b/src/main/java/sevenUnitsGUI/DelegateListModel.java index 5938b59..798383b 100644 --- a/src/main/java/sevenUnitsGUI/DelegateListModel.java +++ b/src/main/java/sevenUnitsGUI/DelegateListModel.java @@ -27,15 +27,17 @@ import javax.swing.AbstractListModel; /** * A list model that delegates to a list. * <p> - * It is recommended to use the delegate methods in DelegateListModel instead of the delegated list's methods because - * the delegate methods handle updating the list. + * It is recommended to use the delegate methods in DelegateListModel instead of + * the delegated list's methods because the delegate methods handle updating the + * list. * </p> * * @author Adrien Hopkins * @since 2019-01-14 * @since v0.1.0 */ -final class DelegateListModel<E> extends AbstractListModel<E> implements List<E> { +final class DelegateListModel<E> extends AbstractListModel<E> + implements List<E> { /** * @since 2019-01-14 * @since v0.1.0 @@ -62,8 +64,7 @@ final class DelegateListModel<E> extends AbstractListModel<E> implements List<E> /** * Creates the {@code DelegateListModel}. * - * @param delegate - * list to delegate + * @param delegate list to delegate * @since 2019-01-14 * @since v0.1.0 */ @@ -101,7 +102,8 @@ final class DelegateListModel<E> extends AbstractListModel<E> implements List<E> for (final E e : c) { this.add(index, e); } - return !c.isEmpty(); // Since this is a list, it will always change if c has elements. + return !c.isEmpty(); // Since this is a list, it will always change if c + // has elements. } @Override diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java index 5c39788..882c995 100644 --- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java +++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java @@ -30,14 +30,14 @@ public interface ExpressionConversionView extends View { * @since 2021-12-15 */ String getFromExpression(); - + /** * @return unit expression to convert <em>to</em> * @since v0.4.0 * @since 2021-12-15 */ String getToExpression(); - + /** * Shows the output of an expression conversion to the user. * diff --git a/src/main/java/sevenUnitsGUI/FilterComparator.java b/src/main/java/sevenUnitsGUI/FilterComparator.java index c0a67e8..484a98f 100644 --- a/src/main/java/sevenUnitsGUI/FilterComparator.java +++ b/src/main/java/sevenUnitsGUI/FilterComparator.java @@ -50,7 +50,7 @@ final class FilterComparator<T> implements Comparator<T> { * @since v0.2.0 */ private final boolean caseSensitive; - + /** * Creates the {@code FilterComparator}. * @@ -61,7 +61,7 @@ final class FilterComparator<T> implements Comparator<T> { public FilterComparator(final String filter) { this(filter, null); } - + /** * Creates the {@code FilterComparator}. * @@ -76,7 +76,7 @@ final class FilterComparator<T> implements Comparator<T> { final Comparator<T> comparator) { this(filter, comparator, false); } - + /** * Creates the {@code FilterComparator}. * @@ -95,7 +95,7 @@ final class FilterComparator<T> implements Comparator<T> { this.comparator = comparator; this.caseSensitive = caseSensitive; } - + /** * Compares two objects according to whether or not they match a filter. * Objects whose string representation starts with the filter's text go @@ -114,19 +114,19 @@ final class FilterComparator<T> implements Comparator<T> { str0 = arg0.toString().toLowerCase(); str1 = arg1.toString().toLowerCase(); } - + // elements that start with the filter always go first if (str0.startsWith(this.filter) && !str1.startsWith(this.filter)) return -1; else if (!str0.startsWith(this.filter) && str1.startsWith(this.filter)) return 1; - + // elements that contain the filter but don't start with them go next if (str0.contains(this.filter) && !str1.contains(this.filter)) return -1; else if (!str0.contains(this.filter) && !str1.contains(this.filter)) return 1; - + // other elements go last if (this.comparator == null) return str0.compareTo(str1); diff --git a/src/main/java/sevenUnitsGUI/GridBagBuilder.java b/src/main/java/sevenUnitsGUI/GridBagBuilder.java index 32e94d7..fdbaee7 100644 --- a/src/main/java/sevenUnitsGUI/GridBagBuilder.java +++ b/src/main/java/sevenUnitsGUI/GridBagBuilder.java @@ -30,13 +30,16 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code gridx} property. * <p> - * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has - * <code>gridx=0</code>. The leading edge of a component's display area is its left edge for a horizontal, - * left-to-right container and its right edge for a horizontal, right-to-left container. The value - * <code>RELATIVE</code> specifies that the component be placed immediately following the component that was added - * to the container just before this component was added. + * Specifies the cell containing the leading edge of the component's display + * area, where the first cell in a row has <code>gridx=0</code>. The leading + * edge of a component's display area is its left edge for a horizontal, + * left-to-right container and its right edge for a horizontal, right-to-left + * container. The value <code>RELATIVE</code> specifies that the component be + * placed immediately following the component that was added to the container + * just before this component was added. * <p> - * The default value is <code>RELATIVE</code>. <code>gridx</code> should be a non-negative value. + * The default value is <code>RELATIVE</code>. <code>gridx</code> should be a + * non-negative value. * * @serial * @see #clone() @@ -48,11 +51,13 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code gridy} property. * <p> - * Specifies the cell at the top of the component's display area, where the topmost cell has <code>gridy=0</code>. - * The value <code>RELATIVE</code> specifies that the component be placed just below the component that was added to - * the container just before this component was added. + * Specifies the cell at the top of the component's display area, where the + * topmost cell has <code>gridy=0</code>. The value <code>RELATIVE</code> + * specifies that the component be placed just below the component that was + * added to the container just before this component was added. * <p> - * The default value is <code>RELATIVE</code>. <code>gridy</code> should be a non-negative value. + * The default value is <code>RELATIVE</code>. <code>gridy</code> should be a + * non-negative value. * * @serial * @see #clone() @@ -65,9 +70,10 @@ final class GridBagBuilder { * <p> * Specifies the number of cells in a row for the component's display area. * <p> - * Use <code>REMAINDER</code> to specify that the component's display area will be from <code>gridx</code> to the - * last cell in the row. Use <code>RELATIVE</code> to specify that the component's display area will be from - * <code>gridx</code> to the next to the last one in its row. + * Use <code>REMAINDER</code> to specify that the component's display area + * will be from <code>gridx</code> to the last cell in the row. Use + * <code>RELATIVE</code> to specify that the component's display area will be + * from <code>gridx</code> to the next to the last one in its row. * <p> * <code>gridwidth</code> should be non-negative and the default value is 1. * @@ -80,13 +86,16 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code gridheight} property. * <p> - * Specifies the number of cells in a column for the component's display area. + * Specifies the number of cells in a column for the component's display + * area. * <p> - * Use <code>REMAINDER</code> to specify that the component's display area will be from <code>gridy</code> to the - * last cell in the column. Use <code>RELATIVE</code> to specify that the component's display area will be from - * <code>gridy</code> to the next to the last one in its column. + * Use <code>REMAINDER</code> to specify that the component's display area + * will be from <code>gridy</code> to the last cell in the column. Use + * <code>RELATIVE</code> to specify that the component's display area will be + * from <code>gridy</code> to the next to the last one in its column. * <p> - * <code>gridheight</code> should be a non-negative value and the default value is 1. + * <code>gridheight</code> should be a non-negative value and the default + * value is 1. * * @serial * @see #clone() @@ -99,15 +108,17 @@ final class GridBagBuilder { * <p> * Specifies how to distribute extra horizontal space. * <p> - * The grid bag layout manager calculates the weight of a column to be the maximum <code>weightx</code> of all the - * components in a column. If the resulting layout is smaller horizontally than the area it needs to fill, the extra - * space is distributed to each column in proportion to its weight. A column that has a weight of zero receives no - * extra space. + * The grid bag layout manager calculates the weight of a column to be the + * maximum <code>weightx</code> of all the components in a column. If the + * resulting layout is smaller horizontally than the area it needs to fill, + * the extra space is distributed to each column in proportion to its weight. + * A column that has a weight of zero receives no extra space. * <p> - * If all the weights are zero, all the extra space appears between the grids of the cell and the left and right - * edges. + * If all the weights are zero, all the extra space appears between the grids + * of the cell and the left and right edges. * <p> - * The default value of this field is <code>0</code>. <code>weightx</code> should be a non-negative value. + * The default value of this field is <code>0</code>. <code>weightx</code> + * should be a non-negative value. * * @serial * @see #clone() @@ -120,15 +131,17 @@ final class GridBagBuilder { * <p> * Specifies how to distribute extra vertical space. * <p> - * The grid bag layout manager calculates the weight of a row to be the maximum <code>weighty</code> of all the - * components in a row. If the resulting layout is smaller vertically than the area it needs to fill, the extra - * space is distributed to each row in proportion to its weight. A row that has a weight of zero receives no extra - * space. + * The grid bag layout manager calculates the weight of a row to be the + * maximum <code>weighty</code> of all the components in a row. If the + * resulting layout is smaller vertically than the area it needs to fill, the + * extra space is distributed to each row in proportion to its weight. A row + * that has a weight of zero receives no extra space. * <p> - * If all the weights are zero, all the extra space appears between the grids of the cell and the top and bottom - * edges. + * If all the weights are zero, all the extra space appears between the grids + * of the cell and the top and bottom edges. * <p> - * The default value of this field is <code>0</code>. <code>weighty</code> should be a non-negative value. + * The default value of this field is <code>0</code>. <code>weighty</code> + * should be a non-negative value. * * @serial * @see #clone() @@ -139,20 +152,26 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code anchor} property. * <p> - * This field is used when the component is smaller than its display area. It determines where, within the display - * area, to place the component. + * This field is used when the component is smaller than its display area. It + * determines where, within the display area, to place the component. * <p> - * There are three kinds of possible values: orientation relative, baseline relative and absolute. Orientation - * relative values are interpreted relative to the container's component orientation property, baseline relative - * values are interpreted relative to the baseline and absolute values are not. The absolute values are: - * <code>CENTER</code>, <code>NORTH</code>, <code>NORTHEAST</code>, <code>EAST</code>, <code>SOUTHEAST</code>, - * <code>SOUTH</code>, <code>SOUTHWEST</code>, <code>WEST</code>, and <code>NORTHWEST</code>. The orientation - * relative values are: <code>PAGE_START</code>, <code>PAGE_END</code>, <code>LINE_START</code>, - * <code>LINE_END</code>, <code>FIRST_LINE_START</code>, <code>FIRST_LINE_END</code>, <code>LAST_LINE_START</code> - * and <code>LAST_LINE_END</code>. The baseline relative values are: <code>BASELINE</code>, - * <code>BASELINE_LEADING</code>, <code>BASELINE_TRAILING</code>, <code>ABOVE_BASELINE</code>, - * <code>ABOVE_BASELINE_LEADING</code>, <code>ABOVE_BASELINE_TRAILING</code>, <code>BELOW_BASELINE</code>, - * <code>BELOW_BASELINE_LEADING</code>, and <code>BELOW_BASELINE_TRAILING</code>. The default value is + * There are three kinds of possible values: orientation relative, baseline + * relative and absolute. Orientation relative values are interpreted + * relative to the container's component orientation property, baseline + * relative values are interpreted relative to the baseline and absolute + * values are not. The absolute values are: <code>CENTER</code>, + * <code>NORTH</code>, <code>NORTHEAST</code>, <code>EAST</code>, + * <code>SOUTHEAST</code>, <code>SOUTH</code>, <code>SOUTHWEST</code>, + * <code>WEST</code>, and <code>NORTHWEST</code>. The orientation relative + * values are: <code>PAGE_START</code>, <code>PAGE_END</code>, + * <code>LINE_START</code>, <code>LINE_END</code>, + * <code>FIRST_LINE_START</code>, <code>FIRST_LINE_END</code>, + * <code>LAST_LINE_START</code> and <code>LAST_LINE_END</code>. The baseline + * relative values are: <code>BASELINE</code>, <code>BASELINE_LEADING</code>, + * <code>BASELINE_TRAILING</code>, <code>ABOVE_BASELINE</code>, + * <code>ABOVE_BASELINE_LEADING</code>, <code>ABOVE_BASELINE_TRAILING</code>, + * <code>BELOW_BASELINE</code>, <code>BELOW_BASELINE_LEADING</code>, and + * <code>BELOW_BASELINE_TRAILING</code>. The default value is * <code>CENTER</code>. * * @serial @@ -164,17 +183,18 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code fill} property. * <p> - * This field is used when the component's display area is larger than the component's requested size. It determines - * whether to resize the component, and if so, how. + * This field is used when the component's display area is larger than the + * component's requested size. It determines whether to resize the component, + * and if so, how. * <p> * The following values are valid for <code>fill</code>: * * <ul> * <li><code>NONE</code>: Do not resize the component. - * <li><code>HORIZONTAL</code>: Make the component wide enough to fill its display area horizontally, but do not - * change its height. - * <li><code>VERTICAL</code>: Make the component tall enough to fill its display area vertically, but do not change - * its width. + * <li><code>HORIZONTAL</code>: Make the component wide enough to fill its + * display area horizontally, but do not change its height. + * <li><code>VERTICAL</code>: Make the component tall enough to fill its + * display area vertically, but do not change its width. * <li><code>BOTH</code>: Make the component fill its display area entirely. * </ul> * <p> @@ -188,8 +208,8 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code insets} property. * <p> - * This field specifies the external padding of the component, the minimum amount of space between the component and - * the edges of its display area. + * This field specifies the external padding of the component, the minimum + * amount of space between the component and the edges of its display area. * <p> * The default value is <code>new Insets(0, 0, 0, 0)</code>. * @@ -201,8 +221,9 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code ipadx} property. * <p> - * This field specifies the internal padding of the component, how much space to add to the minimum width of the - * component. The width of the component is at least its minimum width plus <code>ipadx</code> pixels. + * This field specifies the internal padding of the component, how much space + * to add to the minimum width of the component. The width of the component + * is at least its minimum width plus <code>ipadx</code> pixels. * <p> * The default value is <code>0</code>. * @@ -215,8 +236,9 @@ final class GridBagBuilder { /** * The built {@code GridBagConstraints}'s {@code ipady} property. * <p> - * This field specifies the internal padding, that is, how much space to add to the minimum height of the component. - * The height of the component is at least its minimum height plus <code>ipady</code> pixels. + * This field specifies the internal padding, that is, how much space to add + * to the minimum height of the component. The height of the component is at + * least its minimum height plus <code>ipady</code> pixels. * <p> * The default value is 0. * @@ -227,10 +249,8 @@ final class GridBagBuilder { private int ipady; /** - * @param gridx - * x position - * @param gridy - * y position + * @param gridx x position + * @param gridy y position * @since 2018-11-30 * @since v0.1.0 */ @@ -239,31 +259,25 @@ final class GridBagBuilder { } /** - * @param gridx - * x position - * @param gridy - * y position - * @param gridwidth - * number of cells occupied horizontally - * @param gridheight - * number of cells occupied vertically + * @param gridx x position + * @param gridy y position + * @param gridwidth number of cells occupied horizontally + * @param gridheight number of cells occupied vertically * @since 2018-11-30 * @since v0.1.0 */ - public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight) { - this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, + public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, + final int gridheight) { + this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); } /** - * @param gridx - * x position - * @param gridy - * y position - * @param gridwidth - * number of cells occupied horizontally - * @param gridheight - * number of cells occupied vertically + * @param gridx x position + * @param gridy y position + * @param gridwidth number of cells occupied horizontally + * @param gridheight number of cells occupied vertically * @param weightx * @param weighty * @param anchor @@ -274,9 +288,10 @@ final class GridBagBuilder { * @since 2018-11-30 * @since v0.1.0 */ - private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight, - final double weightx, final double weighty, final int anchor, final int fill, final Insets insets, - final int ipadx, final int ipady) { + private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, + final int gridheight, final double weightx, final double weighty, + final int anchor, final int fill, final Insets insets, final int ipadx, + final int ipady) { super(); this.gridx = gridx; this.gridy = gridy; @@ -297,8 +312,9 @@ final class GridBagBuilder { * @since v0.1.0 */ public GridBagConstraints build() { - return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, this.gridheight, this.weightx, - this.weighty, this.anchor, this.fill, this.insets, this.ipadx, this.ipady); + return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, + this.gridheight, this.weightx, this.weighty, this.anchor, this.fill, + this.insets, this.ipadx, this.ipady); } /** @@ -401,8 +417,7 @@ final class GridBagBuilder { } /** - * @param anchor - * anchor to set + * @param anchor anchor to set * @since 2018-11-30 * @since v0.1.0 */ @@ -412,8 +427,7 @@ final class GridBagBuilder { } /** - * @param fill - * fill to set + * @param fill fill to set * @since 2018-11-30 * @since v0.1.0 */ @@ -423,8 +437,7 @@ final class GridBagBuilder { } /** - * @param insets - * insets to set + * @param insets insets to set * @since 2018-11-30 * @since v0.1.0 */ @@ -434,8 +447,7 @@ final class GridBagBuilder { } /** - * @param ipadx - * ipadx to set + * @param ipadx ipadx to set * @since 2018-11-30 * @since v0.1.0 */ @@ -445,8 +457,7 @@ final class GridBagBuilder { } /** - * @param ipady - * ipady to set + * @param ipady ipady to set * @since 2018-11-30 * @since v0.1.0 */ @@ -456,8 +467,7 @@ final class GridBagBuilder { } /** - * @param weightx - * weightx to set + * @param weightx weightx to set * @since 2018-11-30 * @since v0.1.0 */ @@ -467,8 +477,7 @@ final class GridBagBuilder { } /** - * @param weighty - * weighty to set + * @param weighty weighty to set * @since 2018-11-30 * @since v0.1.0 */ diff --git a/src/main/java/sevenUnitsGUI/Main.java b/src/main/java/sevenUnitsGUI/Main.java index 998b373..ff61b3b 100644 --- a/src/main/java/sevenUnitsGUI/Main.java +++ b/src/main/java/sevenUnitsGUI/Main.java @@ -23,7 +23,7 @@ package sevenUnitsGUI; * @since 2022-04-19 */ public final class Main { - + /** * The main method that starts 7Units * @@ -34,5 +34,5 @@ public final class Main { public static void main(String[] args) { View.createTabbedView(); } - + } diff --git a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java index a5034c9..69f09e6 100644 --- a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java +++ b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java @@ -46,7 +46,7 @@ public final class PrefixSearchRule implements */ public static final PrefixSearchRule NO_PREFIXES = getUniversalRule( Set.of()); - + /** * A rule that gives every unit a common set of prefixes. * @@ -54,7 +54,7 @@ public final class PrefixSearchRule implements */ public static final PrefixSearchRule COMMON_PREFIXES = getCoherentOnlyRule( Set.of(Metric.MILLI, Metric.KILO)); - + /** * A rule that gives every unit all metric prefixes. * @@ -62,7 +62,7 @@ public final class PrefixSearchRule implements */ public static final PrefixSearchRule ALL_METRIC_PREFIXES = getCoherentOnlyRule( Metric.ALL_PREFIXES); - + /** * Gets a rule that applies the provided prefixes to coherent units only (as * defined by {@link LinearUnit#isCoherent}), except the kilogram @@ -78,7 +78,7 @@ public final class PrefixSearchRule implements return new PrefixSearchRule(prefixes, u -> u.isCoherent() && !u.getName().equals("kilogram")); } - + /** * Gets a rule that applies the provided prefixes to all units. * @@ -91,17 +91,17 @@ public final class PrefixSearchRule implements Set<UnitPrefix> prefixes) { return new PrefixSearchRule(prefixes, u -> true); } - + /** * The set of prefixes that will be applied to the unit. */ private final Set<UnitPrefix> prefixes; - + /** * Determines which units are given prefixes. */ private final Predicate<LinearUnit> prefixableUnitRule; - + /** * @param prefixes prefixes to add to units * @param prefixableUnitRule function that determines which units get @@ -114,7 +114,7 @@ public final class PrefixSearchRule implements this.prefixes = Collections.unmodifiableSet(new HashSet<>(prefixes)); this.prefixableUnitRule = prefixableUnitRule; } - + @Override public Map<String, LinearUnit> apply(Entry<String, LinearUnit> t) { final Map<String, LinearUnit> outputUnits = new HashMap<>(); @@ -129,7 +129,7 @@ public final class PrefixSearchRule implements } return Collections.unmodifiableMap(outputUnits); } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -140,7 +140,7 @@ public final class PrefixSearchRule implements return Objects.equals(this.prefixableUnitRule, other.prefixableUnitRule) && Objects.equals(this.prefixes, other.prefixes); } - + /** * @return rule that determines which units get prefixes * @since v0.4.0 @@ -149,7 +149,7 @@ public final class PrefixSearchRule implements public Predicate<LinearUnit> getPrefixableUnitRule() { return this.prefixableUnitRule; } - + /** * @return the prefixes that are applied by this rule * @since v0.4.0 @@ -158,12 +158,12 @@ public final class PrefixSearchRule implements public Set<UnitPrefix> getPrefixes() { return this.prefixes; } - + @Override public int hashCode() { return Objects.hash(this.prefixableUnitRule, this.prefixes); } - + @Override public String toString() { return "Apply the following prefixes: " + this.prefixes; diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index abdd1f6..eba8438 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -49,6 +49,9 @@ import sevenUnits.unit.UnitValue; import sevenUnits.utils.Nameable; import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; +import sevenUnitsGUI.StandardDisplayRules.FixedDecimals; +import sevenUnitsGUI.StandardDisplayRules.FixedPrecision; +import sevenUnitsGUI.StandardDisplayRules.UncertaintyBased; /** * An object that handles interactions between the view and the backend code @@ -57,16 +60,42 @@ import sevenUnits.utils.UncertainDouble; * @since 2021-12-15 */ public final class Presenter { - /** The default place where settings are stored. */ - private static final Path DEFAULT_SETTINGS_FILEPATH = Path - .of("settings.txt"); + /** + * The place where settings are stored. Both this path and its parent + * directory may not exist. + */ + private static final Path CONFIG_FILE = userConfigDir().resolve("SevenUnits") + .resolve("config.txt"); /** The default place where units are stored. */ private static final String DEFAULT_UNITS_FILEPATH = "/unitsfile.txt"; /** The default place where dimensions are stored. */ private static final String DEFAULT_DIMENSIONS_FILEPATH = "/dimensionfile.txt"; /** The default place where exceptions are stored. */ private static final String DEFAULT_EXCEPTIONS_FILEPATH = "/metric_exceptions.txt"; - + + private static final Path userConfigDir() { + if (System.getProperty("os.name").startsWith("Windows")) { + final String envFolder = System.getenv("LOCALAPPDATA"); + if (envFolder == null || "".equals(envFolder)) { + return Path.of(System.getenv("USERPROFILE"), "AppData", "Local"); + } else { + return Path.of(envFolder); + } + } else { + final String envFolder = System.getenv("XDG_CONFIG_HOME"); + if (envFolder == null || "".equals(envFolder)) { + return Path.of(System.getenv("HOME"), ".config"); + } else { + return Path.of(envFolder); + } + } + } + + /** Gets a Path from a pathname in the config file. */ + private static Path pathFromConfig(String pathname) { + return CONFIG_FILE.getParent().resolve(pathname); + } + /** * Adds default units and dimensions to a database. * @@ -88,14 +117,14 @@ public final class Presenter { // nonlinear units - must be loaded manually database.addUnit("tempCelsius", Metric.CELSIUS); database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT); - + // load initial dimensions database.addDimension("Length", Metric.Dimensions.LENGTH); database.addDimension("Mass", Metric.Dimensions.MASS); database.addDimension("Time", Metric.Dimensions.TIME); database.addDimension("Temperature", Metric.Dimensions.TEMPERATURE); } - + /** * @return text in About file * @since 2022-02-19 @@ -105,7 +134,7 @@ public final class Presenter { .map(Presenter::withoutComments).collect(Collectors.joining("\n")) .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()); } - + /** * Gets the text of a resource file as a set of strings (each one is one line * of the text). @@ -116,7 +145,7 @@ public final class Presenter { */ private static final List<String> getLinesFromResource(String filename) { final List<String> lines = new ArrayList<>(); - + try (InputStream stream = inputStream(filename); Scanner scanner = new Scanner(stream)) { while (scanner.hasNextLine()) { @@ -126,10 +155,10 @@ public final class Presenter { throw new AssertionError( "Error occurred while loading file " + filename, e); } - + return lines; } - + /** * Gets an input stream for a resource file. * @@ -140,7 +169,7 @@ public final class Presenter { private static final InputStream inputStream(String filepath) { return Presenter.class.getResourceAsStream(filepath); } - + /** * @return true iff a and b have any elements in common * @since 2022-04-19 @@ -152,7 +181,7 @@ public final class Presenter { } return false; } - + /** * @return {@code line} with any comments removed. * @since 2021-03-13 @@ -161,25 +190,25 @@ public final class Presenter { final int index = line.indexOf('#'); return index == -1 ? line : line.substring(0, index); } - + // ====== SETTINGS ====== - + /** * The view that this presenter communicates with */ private final View view; - + /** * The database that this presenter communicates with (effectively the model) */ final UnitDatabase database; - + /** * The rule used for parsing input numbers. Any number-string inputted into * this program will be parsed using this method. <b>Not implemented yet.</b> */ private Function<String, UncertainDouble> numberParsingRule; - + /** * The rule used for displaying the results of unit conversions. The result * of unit conversions will be put into this function, and the resulting @@ -187,41 +216,41 @@ public final class Presenter { */ private Function<UncertainDouble, String> numberDisplayRule = StandardDisplayRules .uncertaintyBased(); - + /** * A predicate that determines whether or not a certain combination of * prefixes is allowed. If it returns false, a combination of prefixes will * not be allowed. Prefixes are put in the list from right to left. */ private Predicate<List<UnitPrefix>> prefixRepetitionRule = DefaultPrefixRepetitionRule.NO_RESTRICTION; - + /** * A rule that accepts a prefixless name-unit pair and returns a map mapping * names to prefixed versions of that unit (including the unit itself) that * should be searchable. */ private Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule = PrefixSearchRule.NO_PREFIXES; - + /** * The set of units that is considered neither metric nor nonmetric for the * purposes of the metric-imperial one-way conversion. These units are * included in both From and To, even if One Way Conversion is enabled. */ private final Set<String> metricExceptions; - + /** * If this is true, views that show units as a list will have metric units * removed from the From unit list and imperial/USC units removed from the To * unit list. */ private boolean oneWayConversionEnabled = false; - + /** * If this is false, duplicate units and prefixes will be removed from the * unit view in views that show units as a list to choose from. */ private boolean showDuplicates = false; - + /** * Creates a Presenter * @@ -232,14 +261,14 @@ public final class Presenter { this.view = view; this.database = new UnitDatabase(); addDefaults(this.database); - + // load units and prefixes try (final InputStream units = inputStream(DEFAULT_UNITS_FILEPATH)) { this.database.loadUnitsFromStream(units); } catch (final IOException e) { throw new AssertionError("Loading of unitsfile.txt failed.", e); } - + // load dimensions try (final InputStream dimensions = inputStream( DEFAULT_DIMENSIONS_FILEPATH)) { @@ -247,7 +276,7 @@ public final class Presenter { } catch (final IOException e) { throw new AssertionError("Loading of dimensionfile.txt failed.", e); } - + // load metric exceptions try { this.metricExceptions = new HashSet<>(); @@ -265,14 +294,16 @@ public final class Presenter { throw new AssertionError("Loading of metric_exceptions.txt failed.", e); } - + // set default settings temporarily - this.loadSettings(DEFAULT_SETTINGS_FILEPATH); - + if (Files.exists(CONFIG_FILE)) { + this.loadSettings(CONFIG_FILE); + } + // a Predicate that returns true iff the argument is a full base unit final Predicate<Unit> isFullBase = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase(); - + // print out unit counts System.out.printf( "Successfully loaded %d units with %d unit names (%d base units).%n", @@ -281,7 +312,7 @@ public final class Presenter { this.database.unitMapPrefixless(false).values().stream() .filter(isFullBase).count()); } - + /** * Applies a search rule to an entry in a name-unit map. * @@ -301,7 +332,7 @@ public final class Presenter { } else return Stream.of(e); } - + /** * Converts from the view's input expression to its output expression. * Displays an error message if any of the required fields are invalid. @@ -315,10 +346,10 @@ public final class Presenter { public void convertExpressions() { if (this.view instanceof ExpressionConversionView) { final ExpressionConversionView xcview = (ExpressionConversionView) this.view; - + final String fromExpression = xcview.getFromExpression(); final String toExpression = xcview.getToExpression(); - + // expressions must not be empty if (fromExpression.isEmpty()) { this.view.showErrorMessage("Parse Error", @@ -330,7 +361,7 @@ public final class Presenter { "Please enter a unit expression in the To: box."); return; } - + // evaluate expressions final LinearUnitValue from; final Unit to; @@ -348,11 +379,11 @@ public final class Presenter { "Could not recognize text in To entry: " + e.getMessage()); return; } - + // convert and show output if (from.getUnit().canConvertTo(to)) { final UncertainDouble uncertainValue; - + // uncertainty is meaningless for non-linear units, so we will have // to erase uncertainty information for them if (to instanceof LinearUnit) { @@ -362,7 +393,7 @@ public final class Presenter { final double value = from.asUnitValue().convertTo(to).getValue(); uncertainValue = UncertainDouble.of(value, 0); } - + final UnitConversionRecord uc = UnitConversionRecord.valueOf( fromExpression, toExpression, "", this.numberDisplayRule.apply(uncertainValue)); @@ -372,12 +403,12 @@ public final class Presenter { "Cannot convert between \"" + fromExpression + "\" and \"" + toExpression + "\"."); } - + } else throw new UnsupportedOperationException( "This function can only be called when the view is an ExpressionConversionView"); } - + /** * Converts from the view's input unit to its output unit. Displays an error * message if any of the required fields are invalid. @@ -391,11 +422,11 @@ public final class Presenter { public void convertUnits() { if (this.view instanceof UnitConversionView) { final UnitConversionView ucview = (UnitConversionView) this.view; - + final Optional<String> fromUnitOptional = ucview.getFromSelection(); final Optional<String> toUnitOptional = ucview.getToSelection(); final String inputValueString = ucview.getInputValue(); - + // extract values from optionals final String fromUnitString, toUnitString; if (fromUnitOptional.isPresent()) { @@ -412,11 +443,11 @@ public final class Presenter { "Please specify a To unit"); return; } - + // convert strings to data, checking if anything is invalid final Unit fromUnit, toUnit; final UncertainDouble uncertainValue; - + if (this.database.containsUnitName(fromUnitString)) { fromUnit = this.database.getUnit(fromUnitString); } else @@ -433,11 +464,11 @@ public final class Presenter { "Invalid value " + inputValueString); return; } - + if (!fromUnit.canConvertTo(toUnit)) throw this.viewError("Could not convert between %s and %s", fromUnit, toUnit); - + // convert - we will need to erase uncertainty for non-linear units, so // we need to treat linear and non-linear units differently final String outputValueString; @@ -447,18 +478,18 @@ public final class Presenter { final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear, uncertainValue); final LinearUnitValue converted = initialValue.convertTo(toLinear); - + outputValueString = this.numberDisplayRule .apply(converted.getValue()); } else { final UnitValue initialValue = UnitValue.of(fromUnit, uncertainValue.value()); final UnitValue converted = initialValue.convertTo(toUnit); - + outputValueString = this.numberDisplayRule .apply(UncertainDouble.of(converted.getValue(), 0)); } - + ucview.showUnitConversionOutput( UnitConversionRecord.valueOf(fromUnitString, toUnitString, inputValueString, outputValueString)); @@ -466,7 +497,7 @@ public final class Presenter { throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); } - + /** * @return true iff duplicate units are shown in unit lists * @since 2022-03-30 @@ -474,7 +505,7 @@ public final class Presenter { public boolean duplicatesShown() { return this.showDuplicates; } - + /** * Gets a name for this dimension using the database * @@ -489,7 +520,7 @@ public final class Presenter { .filter(d -> d.equals(dimension)).findAny().map(Nameable::getName) .orElse(dimension.toString(Nameable::getName)); } - + /** * @return the rule that is used by this presenter to convert numbers into * strings @@ -498,7 +529,7 @@ public final class Presenter { public Function<UncertainDouble, String> getNumberDisplayRule() { return this.numberDisplayRule; } - + /** * @return the rule that is used by this presenter to convert strings into * numbers @@ -508,7 +539,7 @@ public final class Presenter { private Function<String, UncertainDouble> getNumberParsingRule() { return this.numberParsingRule; } - + /** * @return the rule that determines whether a set of prefixes is valid * @since 2022-04-19 @@ -516,7 +547,7 @@ public final class Presenter { public Predicate<List<UnitPrefix>> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * @return the rule that determines which units are prefixed * @since 2022-07-08 @@ -524,7 +555,7 @@ public final class Presenter { public Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> getSearchRule() { return this.searchRule; } - + /** * @return a search rule that shows all single prefixes * @since 2022-07-08 @@ -533,7 +564,7 @@ public final class Presenter { return PrefixSearchRule.getCoherentOnlyRule( new HashSet<>(this.database.prefixMap(true).values())); } - + /** * @return the view associated with this presenter * @since 2022-04-19 @@ -541,7 +572,7 @@ public final class Presenter { public View getView() { return this.view; } - + /** * @return whether or not the provided unit is semi-metric (i.e. an * exception) @@ -557,7 +588,7 @@ public final class Presenter { && this.metricExceptions.contains(symbol.orElseThrow()) || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } - + /** * Loads settings from the user's settings file and applies them to the * presenter. @@ -566,58 +597,124 @@ public final class Presenter { * @since 2021-12-15 */ void loadSettings(Path settingsFile) { - try { - // read file line by line - final int lineNum = 0; - for (final String line : Files.readAllLines(settingsFile)) { - final int equalsIndex = line.indexOf('='); - if (equalsIndex == -1) - throw new IllegalStateException( - "Settings file is malformed at line " + lineNum); - - final String param = line.substring(0, equalsIndex); - final String value = line.substring(equalsIndex + 1); - - switch (param) { - // set manually to avoid the unnecessary saving of the non-manual - // methods - case "number_display_rule": - this.numberDisplayRule = StandardDisplayRules - .getStandardRule(value); - break; - case "prefix_rule": - this.prefixRepetitionRule = DefaultPrefixRepetitionRule - .valueOf(value); - this.database.setPrefixRepetitionRule(this.prefixRepetitionRule); - break; - case "one_way": - this.oneWayConversionEnabled = Boolean.valueOf(value); - break; - case "include_duplicates": - this.showDuplicates = Boolean.valueOf(value); - break; - case "search_prefix_rule": - if (PrefixSearchRule.NO_PREFIXES.toString().equals(value)) { - this.searchRule = PrefixSearchRule.NO_PREFIXES; - } else if (PrefixSearchRule.COMMON_PREFIXES.toString() - .equals(value)) { - this.searchRule = PrefixSearchRule.COMMON_PREFIXES; - } else { - this.searchRule = this.getUniversalSearchRule(); - } - break; - default: - System.err.printf("Warning: unrecognized setting \"%s\".%n", - param); - break; - } - } - if (this.view.getPresenter() != null) { - this.updateView(); + for (Map.Entry<String, String> setting : settingsFromFile(settingsFile)) { + final String value = setting.getValue(); + + switch (setting.getKey()) { + // set manually to avoid the unnecessary saving of the non-manual + // methods + case "custom_dimension_file": + this.database.loadDimensionFile(pathFromConfig(value)); + break; + case "custom_exception_file": + this.loadExceptionFile(pathFromConfig(value)); + break; + case "custom_unit_file": + this.database.loadUnitsFile(pathFromConfig(value)); + break; + case "number_display_rule": + this.setDisplayRuleFromString(value); + break; + case "prefix_rule": + this.prefixRepetitionRule = DefaultPrefixRepetitionRule + .valueOf(value); + this.database.setPrefixRepetitionRule(this.prefixRepetitionRule); + break; + case "one_way": + this.oneWayConversionEnabled = Boolean.valueOf(value); + break; + case "include_duplicates": + this.showDuplicates = Boolean.valueOf(value); + break; + case "search_prefix_rule": + this.setSearchRuleFromString(value); + break; + default: + System.err.printf("Warning: unrecognized setting \"%s\".%n", + setting.getKey()); + break; } - } catch (final IOException e) {} + } + + if (this.view.getPresenter() != null) { + this.updateView(); + } + } + + private List<Map.Entry<String, String>> settingsFromFile(Path settingsFile) { + try (Stream<String> lines = Files.lines(settingsFile)) { + return lines.map(Presenter::withoutComments) + .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine) + .toList(); + } catch (final IOException e) { + this.view.showErrorMessage("Settings Loading Error", + "Error loading settings file. Using default settings."); + return null; + } } - + + private static Map.Entry<String, String> parseSettingLine(String line) { + final int equalsIndex = line.indexOf('='); + if (equalsIndex == -1) + throw new IllegalStateException( + "Settings file is malformed at line: " + line); + + final String param = line.substring(0, equalsIndex); + final String value = line.substring(equalsIndex + 1); + + return Map.entry(param, value); + } + + private void setSearchRuleFromString(String ruleString) { + switch (ruleString) { + case "NO_PREFIXES": + this.searchRule = PrefixSearchRule.NO_PREFIXES; + break; + case "COMMON_PREFIXES": + this.searchRule = PrefixSearchRule.COMMON_PREFIXES; + break; + case "ALL_METRIC_PREFIXES": + this.searchRule = PrefixSearchRule.ALL_METRIC_PREFIXES; + break; + default: + System.err.printf( + "Warning: unrecognized value for search_prefix_rule: %s\n", + ruleString); + } + } + + private void setDisplayRuleFromString(String ruleString) { + String[] tokens = ruleString.split(" "); + switch (tokens[0]) { + case "FIXED_DECIMALS": + final int decimals = Integer.parseInt(tokens[1]); + this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals); + break; + case "FIXED_PRECISION": + final int sigDigs = Integer.parseInt(tokens[1]); + this.numberDisplayRule = StandardDisplayRules.fixedPrecision(sigDigs); + break; + case "UNCERTAINTY_BASED": + this.numberDisplayRule = StandardDisplayRules.uncertaintyBased(); + break; + default: + this.numberDisplayRule = StandardDisplayRules + .getStandardRule(ruleString); + break; + } + } + + private void loadExceptionFile(Path exceptionFile) { + try (Stream<String> lines = Files.lines(exceptionFile)) { + lines.map(Presenter::withoutComments) + .forEach(this.metricExceptions::add); + } catch (IOException e) { + this.view.showErrorMessage("File Load Error", + "Error loading configured metric exception file \"" + + exceptionFile + "\": " + e.getLocalizedMessage()); + } + } + /** * @return true iff the One-Way Conversion feature is available (views that * show units as a list will have metric units removed from the From @@ -628,7 +725,7 @@ public final class Presenter { public boolean oneWayConversionEnabled() { return this.oneWayConversionEnabled; } - + /** * Completes creation of the presenter. This part of the initialization * depends on the view's functions, so it cannot be run if the components @@ -642,10 +739,10 @@ public final class Presenter { final UnitConversionView ucview = (UnitConversionView) this.view; ucview.setDimensionNames(this.database.dimensionMap().keySet()); } - + this.updateView(); } - + void prefixSelected() { final Optional<String> selectedPrefixName = this.view .getViewedPrefixName(); @@ -657,42 +754,81 @@ public final class Presenter { .ifPresent(prefix -> this.view.showPrefix(prefix.getNameSymbol(), String.valueOf(prefix.getMultiplier()))); } - + /** - * Saves the presenter's current settings to its default filepath. + * Saves the presenter's current settings to the config file, creating it if + * it doesn't exist. * + * @return false iff the presenter could not write to the file * @since 2022-04-19 */ - public void saveSettings() { - this.saveSettings(DEFAULT_SETTINGS_FILEPATH); + public boolean saveSettings() { + final Path configDir = CONFIG_FILE.getParent(); + if (!Files.exists(configDir)) { + try { + Files.createDirectories(configDir); + } catch (IOException e) { + return false; + } + } + + return this.writeSettings(CONFIG_FILE); } - + /** * Saves the presenter's settings to the user settings file. * * @param settingsFile file settings should be saved to * @since 2021-12-15 */ - void saveSettings(Path settingsFile) { + boolean writeSettings(Path settingsFile) { try (BufferedWriter writer = Files.newBufferedWriter(settingsFile)) { writer.write(String.format("number_display_rule=%s\n", - this.numberDisplayRule)); + displayRuleToString(this.numberDisplayRule))); writer.write( String.format("prefix_rule=%s\n", this.prefixRepetitionRule)); writer.write( String.format("one_way=%s\n", this.oneWayConversionEnabled)); writer.write( String.format("include_duplicates=%s\n", this.showDuplicates)); - writer.write( - String.format("search_prefix_rule=%s\n", this.searchRule)); + writer.write(String.format("search_prefix_rule=%s\n", + searchRuleToString(this.searchRule))); + return true; } catch (final IOException e) { e.printStackTrace(); this.view.showErrorMessage("I/O Error", "Error occurred while saving settings: " + e.getLocalizedMessage()); + return false; } } - + + private static String searchRuleToString( + Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) { + if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) { + return "NO_PREFIXES"; + } else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) { + return "COMMON_PREFIXES"; + } else if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule)) { + return "ALL_METRIC_PREFIXES"; + } else + return searchRule.toString(); + } + + private static String displayRuleToString( + Function<UncertainDouble, String> numberDisplayRule) { + if (numberDisplayRule instanceof FixedDecimals) { + return String.format("FIXED_DECIMALS %d", + ((FixedDecimals) numberDisplayRule).decimalPlaces()); + } else if (numberDisplayRule instanceof FixedPrecision) { + return String.format("FIXED_PRECISION %d", + ((FixedPrecision) numberDisplayRule).significantFigures()); + } else if (numberDisplayRule instanceof UncertaintyBased) { + return "UNCERTAINTY_BASED"; + } else + return numberDisplayRule.toString(); + } + /** * @param numberDisplayRule the new rule that will be used by this presenter * to convert numbers into strings @@ -702,7 +838,7 @@ public final class Presenter { Function<UncertainDouble, String> numberDisplayRule) { this.numberDisplayRule = numberDisplayRule; } - + /** * @param numberParsingRule the new rule that will be used by this presenter * to convert strings into numbers @@ -713,7 +849,7 @@ public final class Presenter { Function<String, UncertainDouble> numberParsingRule) { this.numberParsingRule = numberParsingRule; } - + /** * @param oneWayConversionEnabled whether not one-way conversion should be * enabled @@ -724,7 +860,7 @@ public final class Presenter { this.oneWayConversionEnabled = oneWayConversionEnabled; this.updateView(); } - + /** * @param prefixRepetitionRule the rule that determines whether a set of * prefixes is valid @@ -735,7 +871,7 @@ public final class Presenter { this.prefixRepetitionRule = prefixRepetitionRule; this.database.setPrefixRepetitionRule(prefixRepetitionRule); } - + /** * @param searchRule A rule that accepts a prefixless name-unit pair and * returns a map mapping names to prefixed versions of that @@ -747,7 +883,7 @@ public final class Presenter { Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) { this.searchRule = searchRule; } - + /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 @@ -756,7 +892,7 @@ public final class Presenter { this.showDuplicates = showDuplicateUnits; this.updateView(); } - + /** * Shows a unit in the unit viewer * @@ -772,7 +908,7 @@ public final class Presenter { final var unitType = UnitType.getType(u, this::isSemiMetric); this.view.showUnit(nameSymbol, definition, dimensionString, unitType); } - + /** * Runs whenever a unit name is selected in the unit viewer. Gets the * description of a unit and displays it. @@ -788,7 +924,7 @@ public final class Presenter { : null); selectedUnit.ifPresent(this::showUnit); } - + /** * Updates the view's From and To units, if it has some * @@ -798,19 +934,19 @@ public final class Presenter { if (this.view instanceof UnitConversionView) { final UnitConversionView ucview = (UnitConversionView) this.view; final var selectedDimensionName = ucview.getSelectedDimensionName(); - + // load units & prefixes into viewers this.view.setViewableUnitNames( this.database.unitMapPrefixless(this.showDuplicates).keySet()); this.view.setViewablePrefixNames( this.database.prefixMap(this.showDuplicates).keySet()); - + // get From and To units var fromUnits = this.database.unitMapPrefixless(this.showDuplicates) .entrySet().stream(); var toUnits = this.database.unitMapPrefixless(this.showDuplicates) .entrySet().stream(); - + // filter by dimension, if one is selected if (selectedDimensionName.isPresent()) { final var viewDimension = this.database @@ -820,7 +956,7 @@ public final class Presenter { toUnits = toUnits.filter( u -> viewDimension.equals(u.getValue().getDimension())); } - + // filter by unit type, if desired if (this.oneWayConversionEnabled) { fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(), @@ -828,7 +964,7 @@ public final class Presenter { toUnits = toUnits.filter(u -> UnitType.getType(u.getValue(), this::isSemiMetric) != UnitType.NON_METRIC); } - + // set unit names ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule) .map(Map.Entry::getKey).collect(Collectors.toSet())); @@ -836,7 +972,7 @@ public final class Presenter { .map(Map.Entry::getKey).collect(Collectors.toSet())); } } - + /** * @param message message to add * @param args string formatting arguments for message diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java index 9b41601..8fba459 100644 --- a/src/main/java/sevenUnitsGUI/SearchBoxList.java +++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java @@ -40,13 +40,13 @@ import javax.swing.JTextField; * @since v0.2.0 */ final class SearchBoxList<E> extends JPanel { - + /** * @since 2019-04-13 * @since v0.2.0 */ private static final long serialVersionUID = 6226930279415983433L; - + /** * The text to place in an empty search box. * @@ -54,7 +54,7 @@ final class SearchBoxList<E> extends JPanel { * @since v0.2.0 */ private static final String EMPTY_TEXT = "Search..."; - + /** * The color to use for an empty foreground. * @@ -62,24 +62,24 @@ final class SearchBoxList<E> extends JPanel { * @since v0.2.0 */ private static final Color EMPTY_FOREGROUND = new Color(192, 192, 192); - + // the components private final Collection<E> itemsToFilter; private final DelegateListModel<E> listModel; private final JTextField searchBox; private final JList<E> searchItems; - + private boolean searchBoxEmpty = true; - + // I need to do this because, for some reason, Swing is auto-focusing my // search box without triggering a focus // event. private boolean searchBoxFocused = false; - + private Predicate<E> customSearchFilter = o -> true; private final Comparator<E> defaultOrdering; private final boolean caseSensitive; - + /** * Creates an empty SearchBoxList * @@ -88,7 +88,7 @@ final class SearchBoxList<E> extends JPanel { public SearchBoxList() { this(List.of(), null, false); } - + /** * Creates the {@code SearchBoxList}. * @@ -98,7 +98,7 @@ final class SearchBoxList<E> extends JPanel { public SearchBoxList(final Collection<E> itemsToFilter) { this(itemsToFilter, null, false); } - + /** * Creates the {@code SearchBoxList}. * @@ -116,35 +116,35 @@ final class SearchBoxList<E> extends JPanel { this.itemsToFilter = new ArrayList<>(itemsToFilter); this.defaultOrdering = defaultOrdering; this.caseSensitive = caseSensitive; - + // create the components this.listModel = new DelegateListModel<>(new ArrayList<>(itemsToFilter)); this.searchItems = new JList<>(this.listModel); - + this.searchBox = new JTextField(EMPTY_TEXT); this.searchBox.setForeground(EMPTY_FOREGROUND); - + // add them to the panel this.add(this.searchBox, BorderLayout.PAGE_START); this.add(new JScrollPane(this.searchItems), BorderLayout.CENTER); - + // set up the search box this.searchBox.addFocusListener(new FocusListener() { @Override public void focusGained(final FocusEvent e) { SearchBoxList.this.searchBoxFocusGained(e); } - + @Override public void focusLost(final FocusEvent e) { SearchBoxList.this.searchBoxFocusLost(e); } }); - + this.searchBox.addCaretListener(e -> this.searchBoxTextChanged()); this.searchBoxEmpty = true; } - + /** * Adds an additional filter for searching. * @@ -155,7 +155,7 @@ final class SearchBoxList<E> extends JPanel { public void addSearchFilter(final Predicate<E> filter) { this.customSearchFilter = this.customSearchFilter.and(filter); } - + /** * Resets the search filter. * @@ -165,7 +165,7 @@ final class SearchBoxList<E> extends JPanel { public void clearSearchFilters() { this.customSearchFilter = o -> true; } - + /** * @return items available in search list, including items that are hidden by * the search filter @@ -174,7 +174,7 @@ final class SearchBoxList<E> extends JPanel { public Collection<E> getItems() { return Collections.unmodifiableCollection(this.itemsToFilter); } - + /** * @return this component's search box component * @since 2019-04-14 @@ -183,7 +183,7 @@ final class SearchBoxList<E> extends JPanel { public final JTextField getSearchBox() { return this.searchBox; } - + /** * @param searchText text to search for * @return a filter that filters out that text, based on this list's case @@ -198,7 +198,7 @@ final class SearchBoxList<E> extends JPanel { return item -> item.toString().toLowerCase() .contains(searchText.toLowerCase()); } - + /** * @return this component's list component * @since 2019-04-14 @@ -207,7 +207,7 @@ final class SearchBoxList<E> extends JPanel { public final JList<E> getSearchList() { return this.searchItems; } - + /** * @return index selected in item list, -1 if no selection * @since 2019-04-14 @@ -216,7 +216,7 @@ final class SearchBoxList<E> extends JPanel { public int getSelectedIndex() { return this.searchItems.getSelectedIndex(); } - + /** * @return value selected in item list * @since 2019-04-13 @@ -225,7 +225,7 @@ final class SearchBoxList<E> extends JPanel { public Optional<E> getSelectedValue() { return Optional.ofNullable(this.searchItems.getSelectedValue()); } - + /** * Re-applies the filters. * @@ -238,21 +238,21 @@ final class SearchBoxList<E> extends JPanel { final FilterComparator<E> comparator = new FilterComparator<>(searchText, this.defaultOrdering, this.caseSensitive); final Predicate<E> searchFilter = this.getSearchFilter(searchText); - + this.listModel.clear(); this.itemsToFilter.forEach(item -> { if (searchFilter.test(item)) { this.listModel.add(item); } }); - + // applies the custom filters this.listModel.removeIf(this.customSearchFilter.negate()); - + // sorts the remaining items this.listModel.sort(comparator); } - + /** * Runs whenever the search box gains focus. * @@ -267,7 +267,7 @@ final class SearchBoxList<E> extends JPanel { this.searchBox.setForeground(Color.BLACK); } } - + /** * Runs whenever the search box loses focus. * @@ -282,7 +282,7 @@ final class SearchBoxList<E> extends JPanel { this.searchBox.setForeground(EMPTY_FOREGROUND); } } - + /** * Runs whenever the text in the search box is changed. * <p> @@ -301,7 +301,7 @@ final class SearchBoxList<E> extends JPanel { final FilterComparator<E> comparator = new FilterComparator<>(searchText, this.defaultOrdering, this.caseSensitive); final Predicate<E> searchFilter = this.getSearchFilter(searchText); - + // initialize list with items that match the filter then sort this.listModel.clear(); this.itemsToFilter.forEach(string -> { @@ -309,14 +309,14 @@ final class SearchBoxList<E> extends JPanel { this.listModel.add(string); } }); - + // applies the custom filters this.listModel.removeIf(this.customSearchFilter.negate()); - + // sorts the remaining items this.listModel.sort(comparator); } - + /** * Resets the search box list's contents to the provided items, removing any * old items @@ -329,7 +329,7 @@ final class SearchBoxList<E> extends JPanel { this.itemsToFilter.addAll(newItems); this.reapplyFilter(); } - + /** * Manually updates the search box's item list. * diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java index cc69d31..d00263b 100644 --- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java +++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java @@ -46,7 +46,7 @@ public final class StandardDisplayRules { * The number of places to round to. */ private final int decimalPlaces; - + /** * @param decimalPlaces * @since 2022-04-18 @@ -54,14 +54,14 @@ public final class StandardDisplayRules { private FixedDecimals(int decimalPlaces) { this.decimalPlaces = decimalPlaces; } - + @Override public String apply(UncertainDouble t) { final var toRound = new BigDecimal(t.value()); return toRound.setScale(this.decimalPlaces, RoundingMode.HALF_EVEN) .toPlainString(); } - + /** * @return the number of decimal places this rule rounds to * @since 2022-04-18 @@ -69,7 +69,7 @@ public final class StandardDisplayRules { public int decimalPlaces() { return this.decimalPlaces; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -81,18 +81,18 @@ public final class StandardDisplayRules { return false; return true; } - + @Override public int hashCode() { return 31 + this.decimalPlaces; } - + @Override public String toString() { return "Round to " + this.decimalPlaces + " decimal places"; } } - + /** * A rule that rounds to a fixed number of significant digits. * @@ -103,12 +103,12 @@ public final class StandardDisplayRules { implements Function<UncertainDouble, String> { public static final Pattern TO_STRING_PATTERN = Pattern .compile("Round to (\\d+) significant figures"); - + /** * The number of significant figures to round to. */ private final MathContext mathContext; - + /** * @param significantFigures * @since 2022-04-18 @@ -117,13 +117,13 @@ public final class StandardDisplayRules { this.mathContext = new MathContext(significantFigures, RoundingMode.HALF_EVEN); } - + @Override public String apply(UncertainDouble t) { final var toRound = new BigDecimal(t.value()); return toRound.round(this.mathContext).toString(); } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -138,13 +138,13 @@ public final class StandardDisplayRules { return false; return true; } - + @Override public int hashCode() { return 127 + (this.mathContext == null ? 0 : this.mathContext.hashCode()); } - + /** * @return the number of significant figures this rule rounds to * @since 2022-04-18 @@ -152,14 +152,14 @@ public final class StandardDisplayRules { public int significantFigures() { return this.mathContext.getPrecision(); } - + @Override public String toString() { return "Round to " + this.mathContext.getPrecision() + " significant figures"; } } - + /** * A rounding rule that rounds based on UncertainDouble's toString method. * This means the output will have around as many significant figures as the @@ -170,25 +170,26 @@ public final class StandardDisplayRules { */ public static final class UncertaintyBased implements Function<UncertainDouble, String> { - private UncertaintyBased() {} - + private UncertaintyBased() { + } + @Override public String apply(UncertainDouble t) { return t.toString(false, RoundingMode.HALF_EVEN); } - + @Override public String toString() { return "Uncertainty-Based Rounding"; } } - + /** * For now, I want this to be a singleton. I might want to add a parameter * later, so I won't make it an enum. */ private static final UncertaintyBased UNCERTAINTY_BASED_ROUNDING_RULE = new UncertaintyBased(); - + /** * @param decimalPlaces decimal places to round to * @return a rounding rule that rounds to fixed number of decimal places @@ -198,7 +199,7 @@ public final class StandardDisplayRules { public static final FixedDecimals fixedDecimals(int decimalPlaces) { return new FixedDecimals(decimalPlaces); } - + /** * @param significantFigures significant figures to round to * @return a rounding rule that rounds to a fixed number of significant @@ -209,7 +210,7 @@ public final class StandardDisplayRules { public static final FixedPrecision fixedPrecision(int significantFigures) { return new FixedPrecision(significantFigures); } - + /** * Gets one of the standard rules from its string representation. * @@ -224,23 +225,23 @@ public final class StandardDisplayRules { String ruleToString) { if (UNCERTAINTY_BASED_ROUNDING_RULE.toString().equals(ruleToString)) return UNCERTAINTY_BASED_ROUNDING_RULE; - + // test if it is a fixed-places rule final var placesMatch = FixedDecimals.TO_STRING_PATTERN .matcher(ruleToString); if (placesMatch.matches()) return new FixedDecimals(Integer.valueOf(placesMatch.group(1))); - + // test if it is a fixed-sig-fig rule final var sigFigMatch = FixedPrecision.TO_STRING_PATTERN .matcher(ruleToString); if (sigFigMatch.matches()) return new FixedPrecision(Integer.valueOf(sigFigMatch.group(1))); - + throw new IllegalArgumentException( "Provided string does not match any given rules."); } - + /** * @return an UncertainDouble-based rounding rule * @since v0.4.0 @@ -249,6 +250,7 @@ public final class StandardDisplayRules { public static final UncertaintyBased uncertaintyBased() { return UNCERTAINTY_BASED_ROUNDING_RULE; } - - private StandardDisplayRules() {} + + private StandardDisplayRules() { + } } diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 6181eae..997acc3 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -78,7 +78,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { */ private static final class JComboBoxItemSet<E> extends AbstractSet<E> { private final JComboBox<E> comboBox; - + /** * @param comboBox combo box to get items from * @since 2022-02-19 @@ -86,17 +86,17 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { public JComboBoxItemSet(JComboBox<E> comboBox) { this.comboBox = comboBox; } - + @Override public Iterator<E> iterator() { return new Iterator<>() { private int index = 0; - + @Override public boolean hasNext() { return this.index < JComboBoxItemSet.this.size(); } - + @Override public E next() { if (this.hasNext()) @@ -107,14 +107,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } }; } - + @Override public int size() { return this.comboBox.getItemCount(); } - + } - + /** * The standard types of rounding, corresponding to the options on the * TabbedView's settings panel. @@ -139,7 +139,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { */ UNCERTAINTY; } - + /** * Creates a TabbedView. * @@ -153,14 +153,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { @SuppressWarnings("unused") final View view = new TabbedView(); } - + /** The Presenter that handles this View */ final Presenter presenter; /** The frame that this view lives on */ final JFrame frame; /** The tabbed pane that contains all of the components */ final JTabbedPane masterPane; - + // DIMENSION-BASED CONVERTER /** The combo box that selects dimensions */ final JComboBox<String> dimensionSelector; @@ -174,7 +174,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { final JButton convertUnitButton; /** The output area in the dimension-based converter */ final JTextArea unitOutput; - + // EXPRESSION-BASED CONVERTER /** The "From" entry in the conversion panel */ final JTextField fromEntry; @@ -184,7 +184,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { final JButton convertExpressionButton; /** The output area in the conversion panel */ final JTextArea expressionOutput; - + // UNIT AND PREFIX VIEWERS /** The searchable list of unit names in the unit viewer */ private final SearchBoxList<String> unitNameList; @@ -194,11 +194,11 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { private final JTextArea unitTextBox; /** The text box for prefix data in the prefix viewer */ private final JTextArea prefixTextBox; - + // SETTINGS STUFF private StandardRoundingType roundingType; private int precision; - + /** * Creates the view and makes it visible to the user * @@ -215,161 +215,161 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { System.err.println("Failed to enable system look-and-feel."); e.printStackTrace(); } - + // initialize important components this.presenter = new Presenter(this); this.frame = new JFrame("7Units " + ProgramInfo.VERSION); this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - + // master components (those that contain everything else within them) this.masterPane = new JTabbedPane(); this.frame.add(this.masterPane); - + // ============ UNIT CONVERSION TAB ============ final JPanel convertUnitPanel = new JPanel(); this.masterPane.addTab("Convert Units", convertUnitPanel); this.masterPane.setMnemonicAt(0, KeyEvent.VK_U); convertUnitPanel.setLayout(new BorderLayout()); - + { // panel for input part final JPanel inputPanel = new JPanel(); convertUnitPanel.add(inputPanel, BorderLayout.CENTER); inputPanel.setLayout(new GridLayout(1, 3)); inputPanel.setBorder(new EmptyBorder(6, 6, 3, 6)); - + this.fromSearch = new SearchBoxList<>(); inputPanel.add(this.fromSearch); - + final JPanel inBetweenPanel = new JPanel(); inputPanel.add(inBetweenPanel); inBetweenPanel.setLayout(new BorderLayout()); - + this.dimensionSelector = new JComboBox<>(); inBetweenPanel.add(this.dimensionSelector, BorderLayout.PAGE_START); this.dimensionSelector .addItemListener(e -> this.presenter.updateView()); - + final JLabel arrowLabel = new JLabel("-->"); inBetweenPanel.add(arrowLabel, BorderLayout.CENTER); arrowLabel.setHorizontalAlignment(SwingConstants.CENTER); - + this.toSearch = new SearchBoxList<>(); inputPanel.add(this.toSearch); } - + { // panel for submit and output, and also value entry final JPanel outputPanel = new JPanel(); convertUnitPanel.add(outputPanel, BorderLayout.PAGE_END); outputPanel.setLayout(new BorderLayout()); outputPanel.setBorder(new EmptyBorder(3, 6, 6, 6)); - + final JLabel valuePrompt = new JLabel("Value to convert: "); outputPanel.add(valuePrompt, BorderLayout.LINE_START); - + this.valueInput = new JTextField(); outputPanel.add(this.valueInput, BorderLayout.CENTER); - + // conversion button this.convertUnitButton = new JButton("Convert"); outputPanel.add(this.convertUnitButton, BorderLayout.LINE_END); this.convertUnitButton .addActionListener(e -> this.presenter.convertUnits()); this.convertUnitButton.setMnemonic(KeyEvent.VK_ENTER); - + // conversion output this.unitOutput = new JTextArea(2, 32); outputPanel.add(this.unitOutput, BorderLayout.PAGE_END); this.unitOutput.setEditable(false); } - + // ============ EXPRESSION CONVERSION TAB ============ final JPanel convertExpressionPanel = new JPanel(); this.masterPane.addTab("Convert Unit Expressions", convertExpressionPanel); this.masterPane.setMnemonicAt(1, KeyEvent.VK_E); convertExpressionPanel.setLayout(new GridLayout(4, 1)); - + // from and to expressions this.fromEntry = new JTextField(); convertExpressionPanel.add(this.fromEntry); this.fromEntry.setBorder(BorderFactory.createTitledBorder("From")); - + this.toEntry = new JTextField(); convertExpressionPanel.add(this.toEntry); this.toEntry.setBorder(BorderFactory.createTitledBorder("To")); - + // button to convert this.convertExpressionButton = new JButton("Convert"); convertExpressionPanel.add(this.convertExpressionButton); - + this.convertExpressionButton .addActionListener(e -> this.presenter.convertExpressions()); this.convertExpressionButton.setMnemonic(KeyEvent.VK_ENTER); - + // output of conversion this.expressionOutput = new JTextArea(2, 32); convertExpressionPanel.add(this.expressionOutput); this.expressionOutput .setBorder(BorderFactory.createTitledBorder("Output")); this.expressionOutput.setEditable(false); - + // =========== UNIT VIEWER =========== final JPanel unitLookupPanel = new JPanel(); this.masterPane.addTab("Unit Viewer", unitLookupPanel); this.masterPane.setMnemonicAt(2, KeyEvent.VK_V); unitLookupPanel.setLayout(new GridLayout()); - + this.unitNameList = new SearchBoxList<>(); unitLookupPanel.add(this.unitNameList); this.unitNameList.getSearchList() .addListSelectionListener(e -> this.presenter.unitNameSelected()); - + // the text box for unit's toString this.unitTextBox = new JTextArea(); unitLookupPanel.add(this.unitTextBox); this.unitTextBox.setEditable(false); this.unitTextBox.setLineWrap(true); - + // ============ PREFIX VIEWER ============= final JPanel prefixLookupPanel = new JPanel(); this.masterPane.addTab("Prefix Viewer", prefixLookupPanel); this.masterPane.setMnemonicAt(3, KeyEvent.VK_P); prefixLookupPanel.setLayout(new GridLayout(1, 2)); - + this.prefixNameList = new SearchBoxList<>(); prefixLookupPanel.add(this.prefixNameList); this.prefixNameList.getSearchList() .addListSelectionListener(e -> this.presenter.prefixSelected()); - + // the text box for prefix's toString this.prefixTextBox = new JTextArea(); prefixLookupPanel.add(this.prefixTextBox); this.prefixTextBox.setEditable(false); this.prefixTextBox.setLineWrap(true); - + // ============ INFO PANEL ============ - + final JPanel infoPanel = new JPanel(); this.masterPane.addTab("\uD83D\uDEC8", // info (i) character new JScrollPane(infoPanel)); - + final JTextArea infoTextArea = new JTextArea(); infoTextArea.setEditable(false); infoTextArea.setOpaque(false); infoPanel.add(infoTextArea); infoTextArea.setText(Presenter.getAboutText()); - + // ============ SETTINGS PANEL ============ this.masterPane.addTab("\u2699", new JScrollPane(this.createSettingsPanel())); this.masterPane.setMnemonicAt(5, KeyEvent.VK_S); - + // ============ FINALIZE CREATION OF VIEW ============ this.presenter.postViewInitialize(); this.frame.pack(); this.frame.setVisible(true); } - + /** * Creates and returns the settings panel (in its own function to make this * code more organized, as this function is massive!) @@ -378,28 +378,28 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { */ private JPanel createSettingsPanel() { final JPanel settingsPanel = new JPanel(); - + settingsPanel .setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS)); - + // ============ ROUNDING SETTINGS ============ { final JPanel roundingPanel = new JPanel(); settingsPanel.add(roundingPanel); roundingPanel.setBorder(new TitledBorder("Rounding Settings")); roundingPanel.setLayout(new GridBagLayout()); - + // rounding rule selection final ButtonGroup roundingRuleButtons = new ButtonGroup(); this.roundingType = this.getPresenterRoundingType() .orElseThrow(() -> new AssertionError( "Presenter loaded non-standard rounding rule")); this.precision = this.getPresenterPrecision().orElse(6); - + final JLabel roundingRuleLabel = new JLabel("Rounding Rule:"); roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - + // sigDigSlider needs to be first so that the rounding-type buttons can // show and hide it final JLabel sliderLabel = new JLabel("Precision:"); @@ -407,26 +407,26 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.roundingType != StandardRoundingType.UNCERTAINTY); roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4) .setAnchor(GridBagConstraints.LINE_START).build()); - + final JSlider sigDigSlider = new JSlider(0, 12); roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5) .setAnchor(GridBagConstraints.LINE_START).build()); - + sigDigSlider.setMajorTickSpacing(4); sigDigSlider.setMinorTickSpacing(1); sigDigSlider.setSnapToTicks(true); sigDigSlider.setPaintTicks(true); sigDigSlider.setPaintLabels(true); - + sigDigSlider.setVisible( this.roundingType != StandardRoundingType.UNCERTAINTY); sigDigSlider.setValue(this.precision); - + sigDigSlider.addChangeListener(e -> { this.precision = sigDigSlider.getValue(); this.updatePresenterRoundingRule(); }); - + // significant digit rounding final JRadioButton fixedPrecision = new JRadioButton( "Fixed Precision"); @@ -442,7 +442,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { roundingRuleButtons.add(fixedPrecision); roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - + // decimal place rounding final JRadioButton fixedDecimals = new JRadioButton( "Fixed Decimal Places"); @@ -458,7 +458,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { roundingRuleButtons.add(fixedDecimals); roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2) .setAnchor(GridBagConstraints.LINE_START).build()); - + // scientific rounding final JRadioButton relativePrecision = new JRadioButton( "Uncertainty-Based Rounding"); @@ -475,7 +475,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3) .setAnchor(GridBagConstraints.LINE_START).build()); } - + // ============ PREFIX REPETITION SETTINGS ============ { final JPanel prefixRepetitionPanel = new JPanel(); @@ -483,14 +483,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRepetitionPanel .setBorder(new TitledBorder("Prefix Repetition Settings")); prefixRepetitionPanel.setLayout(new GridBagLayout()); - + final var prefixRule = this.getPresenterPrefixRule() .orElseThrow(() -> new AssertionError( "Presenter loaded non-standard prefix rule")); - + // prefix rules final ButtonGroup prefixRuleButtons = new ButtonGroup(); - + final JRadioButton noRepetition = new JRadioButton("No Repetition"); if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) { noRepetition.setSelected(true); @@ -503,7 +503,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRuleButtons.add(noRepetition); prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - + final JRadioButton noRestriction = new JRadioButton("No Restriction"); if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) { noRestriction.setSelected(true); @@ -516,7 +516,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRuleButtons.add(noRestriction); prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - + final JRadioButton customRepetition = new JRadioButton( "Complex Repetition"); if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) { @@ -531,19 +531,19 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2) .setAnchor(GridBagConstraints.LINE_START).build()); } - + // ============ SEARCH SETTINGS ============ { final JPanel searchingPanel = new JPanel(); settingsPanel.add(searchingPanel); searchingPanel.setBorder(new TitledBorder("Search Settings")); searchingPanel.setLayout(new GridBagLayout()); - + // searching rules final ButtonGroup searchRuleButtons = new ButtonGroup(); - + final var searchRule = this.presenter.getSearchRule(); - + final JRadioButton noPrefixes = new JRadioButton( "Never Include Prefixed Units"); noPrefixes.addActionListener(e -> { @@ -554,7 +554,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { searchRuleButtons.add(noPrefixes); searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - + final JRadioButton commonPrefixes = new JRadioButton( "Include Common Prefixes"); commonPrefixes.addActionListener(e -> { @@ -565,7 +565,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { searchRuleButtons.add(commonPrefixes); searchingPanel.add(commonPrefixes, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - + final JRadioButton alwaysInclude = new JRadioButton( "Include All Single Prefixes"); alwaysInclude.addActionListener(e -> { @@ -577,7 +577,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { searchRuleButtons.add(alwaysInclude); searchingPanel.add(alwaysInclude, new GridBagBuilder(0, 3) .setAnchor(GridBagConstraints.LINE_START).build()); - + if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) { noPrefixes.setSelected(true); } else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) { @@ -589,13 +589,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.presenter.saveSettings(); } } - + // ============ OTHER SETTINGS ============ { final JPanel miscPanel = new JPanel(); settingsPanel.add(miscPanel); miscPanel.setLayout(new GridBagLayout()); - + final JCheckBox oneWay = new JCheckBox("Convert One Way Only"); oneWay.setSelected(this.presenter.oneWayConversionEnabled()); oneWay.addItemListener(e -> { @@ -605,7 +605,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { }); miscPanel.add(oneWay, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - + final JCheckBox showAllVariations = new JCheckBox( "Show Duplicate Units & Prefixes"); showAllVariations.setSelected(this.presenter.duplicatesShown()); @@ -616,49 +616,49 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { }); miscPanel.add(showAllVariations, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - + final JButton unitFileButton = new JButton("Manage Unit Data Files"); unitFileButton.setEnabled(false); miscPanel.add(unitFileButton, new GridBagBuilder(0, 2) .setAnchor(GridBagConstraints.LINE_START).build()); } - + return settingsPanel; } - + @Override public Set<String> getDimensionNames() { return Collections .unmodifiableSet(new JComboBoxItemSet<>(this.dimensionSelector)); } - + @Override public String getFromExpression() { return this.fromEntry.getText(); } - + @Override public Optional<String> getFromSelection() { return this.fromSearch.getSelectedValue(); } - + @Override public Set<String> getFromUnitNames() { // this should work because the only way I can mutate the item list is // with setFromUnits which only accepts a Set return new HashSet<>(this.fromSearch.getItems()); } - + @Override public String getInputValue() { return this.valueInput.getText(); } - + @Override public Presenter getPresenter() { return this.presenter; } - + /** * @return the precision of the presenter's rounding rule, if that is * meaningful @@ -678,7 +678,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { else return OptionalInt.empty(); } - + /** * @return presenter's prefix repetition rule * @since v0.4.0 @@ -690,7 +690,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { ? Optional.of((DefaultPrefixRepetitionRule) prefixRule) : Optional.empty(); } - + /** * Determines which rounding type the presenter is currently using, if any. * @@ -709,41 +709,41 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { else return Optional.empty(); } - + @Override public Optional<String> getSelectedDimensionName() { final String selectedItem = (String) this.dimensionSelector .getSelectedItem(); return Optional.ofNullable(selectedItem); } - + @Override public String getToExpression() { return this.toEntry.getText(); } - + @Override public Optional<String> getToSelection() { return this.toSearch.getSelectedValue(); } - + @Override public Set<String> getToUnitNames() { // this should work because the only way I can mutate the item list is // with setToUnits which only accepts a Set return new HashSet<>(this.toSearch.getItems()); } - + @Override public Optional<String> getViewedPrefixName() { return this.prefixNameList.getSelectedValue(); } - + @Override public Optional<String> getViewedUnitName() { return this.unitNameList.getSelectedValue(); } - + @Override public void setDimensionNames(Set<String> dimensionNames) { this.dimensionSelector.removeAllItems(); @@ -751,45 +751,45 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.dimensionSelector.addItem(d); } } - + @Override public void setFromUnitNames(Set<String> units) { this.fromSearch.setItems(units); } - + @Override public void setToUnitNames(Set<String> units) { this.toSearch.setItems(units); } - + @Override public void setViewablePrefixNames(Set<String> prefixNames) { this.prefixNameList.setItems(prefixNames); } - + @Override public void setViewableUnitNames(Set<String> unitNames) { this.unitNameList.setItems(unitNames); } - + @Override public void showErrorMessage(String title, String message) { JOptionPane.showMessageDialog(this.frame, message, title, JOptionPane.ERROR_MESSAGE); } - + @Override public void showExpressionConversionOutput(UnitConversionRecord uc) { this.expressionOutput.setText(String.format("%s = %s %s", uc.fromName(), uc.outputValueString(), uc.toName())); } - + @Override public void showPrefix(NameSymbol name, String multiplierString) { this.prefixTextBox.setText( String.format("%s%nMultiplier: %s", name, multiplierString)); } - + @Override public void showUnit(NameSymbol name, String definition, String dimensionName, UnitType type) { @@ -797,12 +797,12 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { String.format("%s%nDefinition: %s%nDimension: %s%nType: %s", name, definition, dimensionName, type)); } - + @Override public void showUnitConversionOutput(UnitConversionRecord uc) { this.unitOutput.setText(uc.toString()); } - + /** * Sets the presenter's rounding rule to the one specified by the current * settings diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java index fa64ee9..43a62e6 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java @@ -44,7 +44,7 @@ public final class UnitConversionRecord { input.getValue().toString(false, RoundingMode.HALF_EVEN), output.getValue().toString(false, RoundingMode.HALF_EVEN)); } - + /** * Gets a {@code UnitConversionRecord} from two unit values * @@ -60,7 +60,7 @@ public final class UnitConversionRecord { output.getUnit().getName(), String.valueOf(input.getValue()), String.valueOf(output.getValue())); } - + /** * Gets a {@code UnitConversionRecord} * @@ -78,7 +78,7 @@ public final class UnitConversionRecord { return new UnitConversionRecord(fromName, toName, inputValueString, outputValueString); } - + /** * The name of the unit or expression that was converted from */ @@ -87,7 +87,7 @@ public final class UnitConversionRecord { * The name of the unit or expression that was converted to */ private final String toName; - + /** * A string representing the input value. It doesn't need to be the same as * the input value's string representation; it could be rounded, for example. @@ -98,7 +98,7 @@ public final class UnitConversionRecord { * the input value's string representation; it could be rounded, for example. */ private final String outputValueString; - + /** * @param fromName name of unit or expression that was converted * from @@ -114,7 +114,7 @@ public final class UnitConversionRecord { this.inputValueString = inputValueString; this.outputValueString = outputValueString; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -144,7 +144,7 @@ public final class UnitConversionRecord { return false; return true; } - + /** * @return name of unit or expression that was converted from * @since v0.4.0 @@ -153,7 +153,7 @@ public final class UnitConversionRecord { public String fromName() { return this.fromName; } - + @Override public int hashCode() { final int prime = 31; @@ -168,7 +168,7 @@ public final class UnitConversionRecord { + (this.toName == null ? 0 : this.toName.hashCode()); return result; } - + /** * @return string representing input value * @since v0.4.0 @@ -177,7 +177,7 @@ public final class UnitConversionRecord { public String inputValueString() { return this.inputValueString; } - + /** * @return string representing output value * @since v0.4.0 @@ -186,7 +186,7 @@ public final class UnitConversionRecord { public String outputValueString() { return this.outputValueString; } - + /** * @return name of unit or expression that was converted to * @since v0.4.0 @@ -195,7 +195,7 @@ public final class UnitConversionRecord { public String toName() { return this.toName; } - + @Override public String toString() { final String inputString = this.inputValueString.isBlank() ? this.fromName diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index 0d07823..b9077f7 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -33,21 +33,21 @@ public interface UnitConversionView extends View { * @since 2022-01-29 */ Set<String> getDimensionNames(); - + /** * @return name of unit to convert <em>from</em> * @since v0.4.0 * @since 2021-12-15 */ Optional<String> getFromSelection(); - + /** * @return list of names of units available to convert from * @since v0.4.0 * @since 2022-03-30 */ Set<String> getFromUnitNames(); - + /** * @return value to convert between the units (specifically, the numeric * string provided by the user) @@ -55,28 +55,28 @@ public interface UnitConversionView extends View { * @since 2021-12-15 */ String getInputValue(); - + /** * @return selected dimension * @since v0.4.0 * @since 2021-12-15 */ Optional<String> getSelectedDimensionName(); - + /** * @return name of unit to convert <em>to</em> * @since v0.4.0 * @since 2021-12-15 */ Optional<String> getToSelection(); - + /** * @return list of names of units available to convert to * @since v0.4.0 * @since 2022-03-30 */ Set<String> getToUnitNames(); - + /** * Sets the available dimensions for filtering. * @@ -85,7 +85,7 @@ public interface UnitConversionView extends View { * @since 2021-12-15 */ void setDimensionNames(Set<String> dimensionNames); - + /** * Sets the available units to convert from. {@link #getFromSelection} is not * required to use one of these units; this method is to be used for views @@ -96,7 +96,7 @@ public interface UnitConversionView extends View { * @since 2021-12-15 */ void setFromUnitNames(Set<String> unitNames); - + /** * Sets the available units to convert to. {@link #getToSelection} is not * required to use one of these units; this method is to be used for views @@ -107,7 +107,7 @@ public interface UnitConversionView extends View { * @since 2021-12-15 */ void setToUnitNames(Set<String> unitNames); - + /** * Shows the output of a unit conversion. * diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index bb810ec..7dd0c44 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -38,28 +38,28 @@ public interface View { static View createTabbedView() { return new TabbedView(); } - + /** * @return the presenter associated with this view * @since v0.4.0 * @since 2022-04-19 */ Presenter getPresenter(); - + /** * @return name of prefix currently being viewed * @since v0.4.0 * @since 2022-04-10 */ Optional<String> getViewedPrefixName(); - + /** * @return name of unit currently being viewed * @since v0.4.0 * @since 2022-04-10 */ Optional<String> getViewedUnitName(); - + /** * Sets the list of prefixes that are available to be viewed in a prefix * viewer @@ -69,7 +69,7 @@ public interface View { * @since 2022-04-10 */ void setViewablePrefixNames(Set<String> prefixNames); - + /** * Sets the list of units that are available to be viewed in a unit viewer * @@ -78,7 +78,7 @@ public interface View { * @since 2022-04-10 */ void setViewableUnitNames(Set<String> unitNames); - + /** * Shows an error message. * @@ -89,7 +89,7 @@ public interface View { * @since 2021-12-15 */ void showErrorMessage(String title, String message); - + /** * Shows information about a prefix to the user. * @@ -99,7 +99,7 @@ public interface View { * @since 2022-04-10 */ void showPrefix(NameSymbol name, String multiplierString); - + /** * Shows information about a unit to the user. * diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index e7304c4..e6593fb 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -46,7 +46,7 @@ public final class ViewBot public static final class PrefixViewingRecord implements Nameable { private final NameSymbol nameSymbol; private final String multiplierString; - + /** * @param nameSymbol * @param multiplierString @@ -57,7 +57,7 @@ public final class ViewBot this.nameSymbol = nameSymbol; this.multiplierString = multiplierString; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -68,25 +68,25 @@ public final class ViewBot return Objects.equals(this.multiplierString, other.multiplierString) && Objects.equals(this.nameSymbol, other.nameSymbol); } - + @Override public NameSymbol getNameSymbol() { return this.nameSymbol; } - + @Override public int hashCode() { return Objects.hash(this.multiplierString, this.nameSymbol); } - + public String multiplierString() { return this.multiplierString; } - + public NameSymbol nameSymbol() { return this.nameSymbol; } - + @Override public String toString() { final StringBuilder builder = new StringBuilder(); @@ -98,7 +98,7 @@ public final class ViewBot return builder.toString(); } } - + /** * A record of the parameters given to * {@link View#showUnit(NameSymbol, String, String, UnitType)}, for testing. @@ -110,7 +110,7 @@ public final class ViewBot private final String definition; private final String dimensionName; private final UnitType unitType; - + /** * @since 2022-04-16 */ @@ -121,7 +121,7 @@ public final class ViewBot this.dimensionName = dimensionName; this.unitType = unitType; } - + /** * @return the definition * @since 2022-04-16 @@ -129,7 +129,7 @@ public final class ViewBot public String definition() { return this.definition; } - + /** * @return the dimensionName * @since 2022-04-16 @@ -137,7 +137,7 @@ public final class ViewBot public String dimensionName() { return this.dimensionName; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -150,7 +150,7 @@ public final class ViewBot && Objects.equals(this.nameSymbol, other.nameSymbol) && this.unitType == other.unitType; } - + /** * @return the nameSymbol * @since 2022-04-16 @@ -159,17 +159,17 @@ public final class ViewBot public NameSymbol getNameSymbol() { return this.nameSymbol; } - + @Override public int hashCode() { return Objects.hash(this.definition, this.dimensionName, this.nameSymbol, this.unitType); } - + public NameSymbol nameSymbol() { return this.nameSymbol; } - + @Override public String toString() { final StringBuilder builder = new StringBuilder(); @@ -184,7 +184,7 @@ public final class ViewBot builder.append("]"); return builder.toString(); } - + /** * @return the unitType * @since 2022-04-16 @@ -193,10 +193,10 @@ public final class ViewBot return this.unitType; } } - + /** The presenter that works with this ViewBot */ private final Presenter presenter; - + /** The dimensions available to select from */ private Set<String> dimensionNames = Set.of(); /** The expression in the From field */ @@ -217,12 +217,12 @@ public final class ViewBot private Set<String> fromUnits = Set.of(); /** The units available in the To selection */ private Set<String> toUnits = Set.of(); - + /** The selected unit in the unit viewer */ private Optional<String> unitViewerSelection = Optional.empty(); /** The selected unit in the prefix viewer */ private Optional<String> prefixViewerSelection = Optional.empty(); - + /** Saved outputs of all unit conversions */ private final List<UnitConversionRecord> unitConversions; /** Saved outputs of all unit expressions */ @@ -231,7 +231,7 @@ public final class ViewBot private final List<UnitViewingRecord> unitViewingRecords; /** Saved outputs of all prefix viewings */ private final List<PrefixViewingRecord> prefixViewingRecords; - + /** * Creates a new {@code ViewBot} with a new presenter. * @@ -239,13 +239,13 @@ public final class ViewBot */ public ViewBot() { this.presenter = new Presenter(this); - + this.unitConversions = new ArrayList<>(); this.expressionConversions = new ArrayList<>(); this.unitViewingRecords = new ArrayList<>(); this.prefixViewingRecords = new ArrayList<>(); } - + /** * @return list of records of expression conversions done by this bot * @since 2022-04-09 @@ -253,7 +253,7 @@ public final class ViewBot public List<UnitConversionRecord> expressionConversionList() { return Collections.unmodifiableList(this.expressionConversions); } - + /** * @return the available dimensions * @since 2022-01-29 @@ -262,17 +262,17 @@ public final class ViewBot public Set<String> getDimensionNames() { return this.dimensionNames; } - + @Override public String getFromExpression() { return this.fromExpression; } - + @Override public Optional<String> getFromSelection() { return this.fromSelection; } - + /** * @return the units available for selection in From * @since 2022-01-29 @@ -281,12 +281,12 @@ public final class ViewBot public Set<String> getFromUnitNames() { return Collections.unmodifiableSet(this.fromUnits); } - + @Override public String getInputValue() { return this.inputValue; } - + /** * @return the presenter associated with tihs view * @since 2022-01-29 @@ -295,22 +295,22 @@ public final class ViewBot public Presenter getPresenter() { return this.presenter; } - + @Override public Optional<String> getSelectedDimensionName() { return this.selectedDimensionName; } - + @Override public String getToExpression() { return this.toExpression; } - + @Override public Optional<String> getToSelection() { return this.toSelection; } - + /** * @return the units available for selection in To * @since 2022-01-29 @@ -319,17 +319,17 @@ public final class ViewBot public Set<String> getToUnitNames() { return Collections.unmodifiableSet(this.toUnits); } - + @Override public Optional<String> getViewedPrefixName() { return this.prefixViewerSelection; } - + @Override public Optional<String> getViewedUnitName() { return this.unitViewerSelection; } - + /** * @return list of records of this viewBot's prefix views * @since 2022-04-16 @@ -337,13 +337,13 @@ public final class ViewBot public List<PrefixViewingRecord> prefixViewList() { return Collections.unmodifiableList(this.prefixViewingRecords); } - + @Override public void setDimensionNames(Set<String> dimensionNames) { this.dimensionNames = Objects.requireNonNull(dimensionNames, "dimensions may not be null"); } - + /** * Sets the From expression (as in {@link #getFromExpression}). * @@ -355,7 +355,7 @@ public final class ViewBot this.fromExpression = Objects.requireNonNull(fromExpression, "fromExpression cannot be null."); } - + /** * @param fromSelection the fromSelection to set * @since 2022-01-29 @@ -364,7 +364,7 @@ public final class ViewBot this.fromSelection = Objects.requireNonNull(fromSelection, "fromSelection cannot be null"); } - + /** * @param fromSelection the fromSelection to set * @since 2022-02-10 @@ -372,12 +372,12 @@ public final class ViewBot public void setFromSelection(String fromSelection) { this.setFromSelection(Optional.of(fromSelection)); } - + @Override public void setFromUnitNames(Set<String> units) { this.fromUnits = Objects.requireNonNull(units, "units may not be null"); } - + /** * @param inputValue the inputValue to set * @since 2022-01-29 @@ -385,7 +385,7 @@ public final class ViewBot public void setInputValue(String inputValue) { this.inputValue = inputValue; } - + /** * @param selectedDimension the selectedDimension to set * @since 2022-01-29 @@ -394,11 +394,11 @@ public final class ViewBot Optional<String> selectedDimensionName) { this.selectedDimensionName = selectedDimensionName; } - + public void setSelectedDimensionName(String selectedDimensionName) { this.setSelectedDimensionName(Optional.of(selectedDimensionName)); } - + /** * Sets the To expression (as in {@link #getToExpression}). * @@ -410,7 +410,7 @@ public final class ViewBot this.toExpression = Objects.requireNonNull(toExpression, "toExpression cannot be null."); } - + /** * @param toSelection the toSelection to set * @since 2022-01-29 @@ -419,77 +419,77 @@ public final class ViewBot this.toSelection = Objects.requireNonNull(toSelection, "toSelection cannot be null."); } - + public void setToSelection(String toSelection) { this.setToSelection(Optional.of(toSelection)); } - + @Override public void setToUnitNames(Set<String> units) { this.toUnits = Objects.requireNonNull(units, "units may not be null"); } - + @Override public void setViewablePrefixNames(Set<String> prefixNames) { // do nothing, ViewBot supports selecting any prefix } - + @Override public void setViewableUnitNames(Set<String> unitNames) { // do nothing, ViewBot supports selecting any unit } - + public void setViewedPrefixName(Optional<String> viewedPrefixName) { this.prefixViewerSelection = viewedPrefixName; } - + public void setViewedPrefixName(String viewedPrefixName) { this.setViewedPrefixName(Optional.of(viewedPrefixName)); } - + public void setViewedUnitName(Optional<String> viewedUnitName) { this.unitViewerSelection = viewedUnitName; } - + public void setViewedUnitName(String viewedUnitName) { this.setViewedUnitName(Optional.of(viewedUnitName)); } - + @Override public void showErrorMessage(String title, String message) { System.err.printf("%s: %s%n", title, message); } - + @Override public void showExpressionConversionOutput(UnitConversionRecord uc) { this.expressionConversions.add(uc); System.out.println("Expression Conversion: " + uc); } - + @Override public void showPrefix(NameSymbol name, String multiplierString) { this.prefixViewingRecords .add(new PrefixViewingRecord(name, multiplierString)); } - + @Override public void showUnit(NameSymbol name, String definition, String dimensionName, UnitType type) { this.unitViewingRecords .add(new UnitViewingRecord(name, definition, dimensionName, type)); } - + @Override public void showUnitConversionOutput(UnitConversionRecord uc) { this.unitConversions.add(uc); System.out.println("Unit Conversion: " + uc); } - + @Override public String toString() { return super.toString() + String.format("[presenter=%s]", this.presenter); } - + /** * @return list of records of every unit conversion made by this bot * @since 2022-04-09 @@ -497,7 +497,7 @@ public final class ViewBot public List<UnitConversionRecord> unitConversionList() { return Collections.unmodifiableList(this.unitConversions); } - + /** * @return list of records of unit viewings made by this bot * @since 2022-04-16 diff --git a/src/main/resources/about.txt b/src/main/resources/about.txt index 5cdcf67..782b422 100644 --- a/src/main/resources/about.txt +++ b/src/main/resources/about.txt @@ -1,8 +1,17 @@ About 7Units Version [VERSION] +7Units is a unit converter program with many features, +inspired by GNU Units (https://www.gnu.org/software/units/). +You can use it to simply convert units, but you can also +use it like a calculator, computing and converting expressions +like "10 m/s + (25^2 - 5^2) mi/hr". + +This software was written by Adrien Hopkins +<adrien.p.hopkins@gmail.com>. + Copyright Notice: -Unit Converter Copyright (C) 2018-2023 Adrien Hopkins +Unit Converter Copyright (C) 2018-2024 Adrien Hopkins This program comes with ABSOLUTELY NO WARRANTY; for details read the LICENSE file, section 15 |