From fbb2f4a997e7e7043a2bdf15303b88907f6bcbc6 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 15 Aug 2024 16:47:24 -0500 Subject: Correct encoding of ± MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/sevenUnits/utils/UncertainDouble.java | 108 ++++++++++----------- src/test/java/sevenUnits/unit/UnitTest.java | 72 +++++++------- .../java/sevenUnits/utils/UncertainDoubleTest.java | 28 +++--- 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index 66d8103..1f7ab79 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -35,20 +35,20 @@ public final class UncertainDouble implements Comparable { * 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 { 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. *

* 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 { 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 + "\"."); - + "Could not parse string \"" + s + "\"."); + double value, uncertainty; try { value = Double.parseDouble(matcher.group(1)); @@ -87,7 +87,7 @@ public final class UncertainDouble implements Comparable { 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 { "String " + s + " not in correct format."); } } - + return UncertainDouble.of(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and absolute * uncertainty. @@ -112,7 +112,7 @@ public final class UncertainDouble implements Comparable { public static final UncertainDouble of(double value, double uncertainty) { return new UncertainDouble(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and relative * uncertainty. @@ -123,11 +123,11 @@ public final class UncertainDouble implements Comparable { double relativeUncertainty) { return new UncertainDouble(value, value * relativeUncertainty); } - + private final double value; - + private final double uncertainty; - + /** * @param value * @param uncertainty @@ -138,7 +138,7 @@ public final class UncertainDouble implements Comparable { // uncertainty should only ever be positive this.uncertainty = Math.abs(uncertainty); } - + /** * Compares this {@code UncertainDouble} with another * {@code UncertainDouble}. @@ -156,7 +156,7 @@ public final class UncertainDouble implements Comparable { 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 { 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 { 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 { 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 { 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 { // 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { */ 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 { return UncertainDouble.ofRelative(Math.pow(this.value, other), this.relativeUncertainty() * other); } - + /** * Returns a string representation of this {@code UncertainDouble}. *

@@ -365,12 +365,12 @@ public final class UncertainDouble implements Comparable { public final String toString() { return this.toString(!this.isExact(), RoundingMode.HALF_EVEN); } - + /** * Returns a string representation of this {@code UncertainDouble}. *

* 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" *

* 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.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"
 	 * 
* * @since 2020-09-07 @@ -397,31 +397,31 @@ public final class UncertainDouble implements Comparable { 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 { public final double uncertainty() { return this.uncertainty; } - + /** * @return value without uncertainty * @since 2020-09-07 diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index c93043b..4fb26a3 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -42,17 +42,17 @@ import sevenUnits.utils.UncertainDouble; class UnitTest { /** A random number generator */ private static final Random rng = ThreadLocalRandom.current(); - + @Test public void testAdditionAndSubtraction() { final LinearUnit inch = Metric.METRE.times(0.0254) .withName(NameSymbol.of("inch", "in")); final LinearUnit foot = Metric.METRE.times(0.3048) .withName(NameSymbol.of("foot", "ft")); - + assertEquals(inch.plus(foot), Metric.METRE.times(0.3302)); assertEquals(foot.minus(inch), Metric.METRE.times(0.2794)); - + // test with LinearUnitValue final LinearUnitValue value1 = LinearUnitValue.getExact(Metric.METRE, 15); final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120); @@ -60,70 +60,70 @@ class UnitTest { 0.5); final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM, 60); - + // make sure addition is done correctly assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001); assertEquals(15.5, value1.plus(value3).getValueExact()); assertEquals(52.076, value1.plus(value2).plus(value3).getValueExact(), 0.001); - + // make sure addition uses the correct unit, and is still associative // (ignoring floating-point rounding errors) assertEquals(Metric.METRE, value1.plus(value2).getUnit()); assertEquals(Metric.METRE, value1.plus(value2).plus(value3).getUnit()); assertEquals(foot, value2.plus(value1).getUnit()); assertTrue(value1.plus(value2).equals(value2.plus(value1), true)); - + // make sure errors happen when they should assertThrows(IllegalArgumentException.class, () -> value1.plus(value4)); assertThrows(IllegalArgumentException.class, () -> value1.minus(value4)); } - + @Test public void testConversion() { final LinearUnit metre = Metric.METRE; final Unit inch = metre.times(0.0254); - + final UnitValue value = UnitValue.of(inch, 75); - + assertEquals(1.9, inch.convertTo(metre, 75), 0.01); assertEquals(1.9, value.convertTo(metre).getValue(), 0.01); - + // try random stuff for (int i = 0; i < 1000; i++) { // initiate random values final double conversionFactor = UnitTest.rng.nextDouble() * 1000000; final double testValue = UnitTest.rng.nextDouble() * 1000000; final double expected = testValue * conversionFactor; - + // test final Unit unit = Metric.METRE.times(conversionFactor); final double actual = unit.convertToBase(testValue); - + assertEquals(actual, expected, expected * DecimalComparison.DOUBLE_EPSILON); } } - + @Test public void testEquals() { final LinearUnit metre = Metric.METRE; final Unit meter = Metric.BaseUnits.METRE.asLinearUnit(); - + assertEquals(metre, meter); } - + @Test public void testIsMetric() { final Unit metre = Metric.METRE; final Unit megasecond = Metric.SECOND.withPrefix(Metric.MEGA); final Unit hour = Metric.HOUR; - + assertTrue(metre.isMetric()); assertTrue(megasecond.isMetric()); assertFalse(hour.isMetric()); } - + @Test public void testMultiplicationAndDivision() { // test unit-times-unit multiplication @@ -131,29 +131,29 @@ class UnitTest { .times(Metric.METRE.toExponent(2)) .dividedBy(Metric.SECOND.toExponent(2)); final LinearUnit actualJoule = Metric.JOULE; - + assertEquals(generatedJoule, actualJoule); - + // test multiplication by conversion factors final LinearUnit kilometre = Metric.METRE.times(1000); final LinearUnit hour = Metric.SECOND.times(3600); final LinearUnit generatedKPH = kilometre.dividedBy(hour); - + final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND) .dividedBy(3.6); - + assertEquals(generatedKPH, actualKPH); } - + @Test public void testPrefixes() { final LinearUnit generatedKilometre = Metric.METRE .withPrefix(Metric.KILO); final LinearUnit actualKilometre = Metric.METRE.times(1000); - + assertEquals(generatedKilometre, actualKilometre); } - + /** * Tests converting an uncertain LinearUnitValue to a string. * @@ -163,13 +163,13 @@ class UnitTest { public void testValueToString1() { final LinearUnitValue value = LinearUnitValue.of(Metric.METRE, UncertainDouble.of(10, 0.24)); - - assertEquals("(10.0 � 0.2) m", value.toString()); - assertEquals("(10.0 � 0.2) m", + + assertEquals("(10.0 ± 0.2) m", value.toString()); + assertEquals("(10.0 ± 0.2) m", value.toString(true, RoundingMode.HALF_EVEN)); assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN)); } - + /** * Tests converting a certain LinearUnitValue to a string. * @@ -179,13 +179,13 @@ class UnitTest { public void testValueToString2() { final LinearUnitValue value = LinearUnitValue.of(Metric.METRE, UncertainDouble.of(10, 0)); - + assertEquals("10.0 m", value.toString()); - assertEquals("(10.0 � 0.0) m", + assertEquals("(10.0 ± 0.0) m", value.toString(true, RoundingMode.HALF_EVEN)); assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN)); } - + /** * Tests converting an unnamed LinearUnitValue to a string. * @@ -196,11 +196,11 @@ class UnitTest { final LinearUnitValue value = LinearUnitValue.of( Metric.METRE.withName(NameSymbol.EMPTY), UncertainDouble.of(10, 0.24)); - + assertEquals("10.0 unnamed unit (= 10.0 m)", value.toString(false, RoundingMode.HALF_EVEN)); } - + /** * Tests converting a named UnitValue to a string. * @@ -209,10 +209,10 @@ class UnitTest { @Test public void testValueToString4() { final UnitValue value = UnitValue.of(BritishImperial.FAHRENHEIT, 80); - + assertEquals("80.0 \u00B0F", value.toString()); } - + /** * Tests converting an unnamed UnitValue to a string. * @@ -222,7 +222,7 @@ class UnitTest { public void testValueToString5() { final UnitValue value = UnitValue .of(USCustomary.FAHRENHEIT.withName(NameSymbol.EMPTY), 50); - + assertEquals("50.0 unnamed unit (= 283.15 K)", value.toString()); } } diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java index 36b373b..b251b31 100644 --- a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java +++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java @@ -43,36 +43,36 @@ class UncertainDoubleTest { assertTrue(of(2.0, 0.5).compareTo(of(1.0, 0.1)) > 0); assertTrue(of(2.0, 0.5).compareTo(of(3.0, 0.1)) < 0); } - + /** * Tests the ___exact operations */ @Test final void testExactOperations() { final UncertainDouble x = UncertainDouble.of(Math.PI, 0.1); - + // slightly different because roundoff errors final UncertainDouble x1 = UncertainDouble.of(Math.PI + Math.E - Math.E, 0.1); final UncertainDouble x2 = UncertainDouble.of(Math.PI * Math.E / Math.E, 0.1); - + // get results final UncertainDouble result1 = x.plusExact(Math.E).minusExact(Math.E); final UncertainDouble result2 = x.timesExact(Math.E) .dividedByExact(Math.E); - + // test that these operations work & don't change uncertainty assertEquals(x1, result1); assertTrue(x.equivalent(result1)); assertEquals(x2, result2); assertTrue(x.equivalent(result2)); - + // exponents are different assertEquals(Math.pow(Math.PI, Math.E), x.toExponentExact(Math.E).value()); } - + /** * Test for {@link UncertainDouble#fromRoundedString} * @@ -82,29 +82,29 @@ class UncertainDoubleTest { final void testFromRoundedString() { assertEquals(of(12345.678, 0.001), fromRoundedString("12345.678")); } - + /** * Test for {@link UncertainDouble#fromString} */ @Test final void testFromString() { // valid strings - assertEquals(of(2.0, 0.5), fromString("2.0 � 0.5")); + assertEquals(of(2.0, 0.5), fromString("2.0 ± 0.5")); assertEquals(of(2.0, 0.5), fromString("2.0 +- 0.5")); assertEquals(of(2.0, 0.0), fromString("2.0")); - + // invalid strings - for (final String s : List.of("2.A", "A", "2.0 � .", "� 3.5")) { + for (final String s : List.of("2.A", "A", "2.0 ± ", " ± 3.5")) { assertThrows(IllegalArgumentException.class, () -> fromString(s)); } - + // back and forth - assertEquals("2.0 � 0.5", of(2.0, 0.5).toString()); + assertEquals("2.0 ± 0.5", of(2.0, 0.5).toString()); assertEquals("2.0", of(2.0, 0).toString()); } - + @Test final void testHashCode() { - assertEquals(of(2.0, 0.5).hashCode(), fromString("2.0 � 0.5").hashCode()); + assertEquals(of(2.0, 0.5).hashCode(), fromString("2.0 ± 0.5").hashCode()); } } -- cgit v1.2.3 From e74668b91c247e7395362e8e411c3e1a13ec10d1 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 15 Aug 2024 16:51:45 -0500 Subject: Add ability to convert expression to sum of units --- CHANGELOG.org | 3 + src/main/java/sevenUnits/unit/LinearUnitValue.java | 103 ++-- src/main/java/sevenUnitsGUI/Presenter.java | 606 ++++++++++++--------- src/main/java/sevenUnitsGUI/TabbedView.java | 207 ++++--- .../java/sevenUnitsGUI/UnitConversionRecord.java | 24 +- 5 files changed, 530 insertions(+), 413 deletions(-) diff --git a/CHANGELOG.org b/CHANGELOG.org index 9696497..ad2f09b 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -1,5 +1,8 @@ * Changelog All notable changes in this project will be shown in this file. +** Unreleased +*** Added +- *Allowed conversion to a sum of units (e.g. 4/3 ft \rightarrow 1 ft + 4 in).* ** v0.5.0 - [2024-03-24 Sun] *** Added - *Added specifications for all types data files used by 7Units.* diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index 3a9428b..fad3eb0 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -17,10 +17,13 @@ package sevenUnits.unit; import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.Optional; import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; /** @@ -34,7 +37,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 +52,7 @@ public final class LinearUnitValue { Objects.requireNonNull(unit, "unit must not be null"), UncertainDouble.of(value, 0)); } - + /** * Gets an uncertain {@code LinearUnitValue} * @@ -65,11 +68,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 +82,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 +91,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 +100,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 +112,43 @@ public final class LinearUnitValue { public final LinearUnitValue convertTo(final LinearUnit other) { return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); } - + + /** + * Convert a LinearUnitValue to a sum of multiple units, where all but the + * last have exact integer values. + * + * @param others units to convert to + * @throws IllegalArgumentException if no units are provided or units + * provided have incompatible bases + * @since 2024-08-15 + */ + public final List convertToMultiple( + final List others) { + if (others.size() < 1) + throw new IllegalArgumentException("Must have at least one unit"); + final ObjectProduct unitBase = others.get(0).getBase(); + for (final LinearUnit unit : others) { + if (!unitBase.equals(unit.getBase())) + throw new IllegalArgumentException( + "All units must have the same base."); + } + + LinearUnitValue remaining = this; + final List values = new ArrayList<>(others.size()); + for (final LinearUnit unit : others.subList(0, others.size() - 1)) { + final LinearUnitValue remainingInUnit = remaining.convertTo(unit); + final LinearUnitValue value = getExact(unit, + Math.floor(remainingInUnit.getValueExact())); + values.add(value); + remaining = remaining.minus(value); + } + + final LinearUnitValue lastValue = remaining + .convertTo(others.get(others.size() - 1)); + values.add(lastValue); + return values; + } + /** * Divides this value by a scalar * @@ -120,7 +159,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 +171,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 +189,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 +210,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 +224,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 +235,7 @@ public final class LinearUnitValue { public final LinearUnit getUnit() { return this.unit; } - + /** * @return the value * @since 2020-09-29 @@ -204,7 +243,7 @@ public final class LinearUnitValue { public final UncertainDouble getValue() { return this.value; } - + /** * @return the exact value * @since 2020-09-07 @@ -212,13 +251,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 +270,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 +292,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 +313,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 +325,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,12 +337,12 @@ 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.
* If the attached unit has a name or symbol, the string looks like "12 km". @@ -321,9 +360,9 @@ public final class LinearUnitValue { final Optional primaryName = this.unit.getPrimaryName(); final Optional 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 +371,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/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index eba8438..8f99649 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -72,30 +72,7 @@ public final class Presenter { 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. * @@ -117,14 +94,28 @@ 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); } - + + private static String displayRuleToString( + Function 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(); + } + /** * @return text in About file * @since 2022-02-19 @@ -134,7 +125,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). @@ -145,7 +136,7 @@ public final class Presenter { */ private static final List getLinesFromResource(String filename) { final List lines = new ArrayList<>(); - + try (InputStream stream = inputStream(filename); Scanner scanner = new Scanner(stream)) { while (scanner.hasNextLine()) { @@ -155,10 +146,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. * @@ -169,7 +160,38 @@ public final class Presenter { private static final InputStream inputStream(String filepath) { return Presenter.class.getResourceAsStream(filepath); } - + + private static Map.Entry 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); + } + + /** Gets a Path from a pathname in the config file. */ + private static Path pathFromConfig(String pathname) { + return CONFIG_FILE.getParent().resolve(pathname); + } + + private static String searchRuleToString( + Function, Map> 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(); + } + + // ====== SETTINGS ====== + /** * @return true iff a and b have any elements in common * @since 2022-04-19 @@ -181,7 +203,23 @@ public final class Presenter { } return false; } - + + 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); + } + } + /** * @return {@code line} with any comments removed. * @since 2021-03-13 @@ -190,25 +228,23 @@ 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. Not implemented yet. */ private Function 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 @@ -216,41 +252,41 @@ public final class Presenter { */ private Function 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> 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> 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 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 * @@ -261,14 +297,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)) { @@ -276,7 +312,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<>(); @@ -294,16 +330,16 @@ public final class Presenter { throw new AssertionError("Loading of metric_exceptions.txt failed.", e); } - + // set default settings temporarily if (Files.exists(CONFIG_FILE)) { this.loadSettings(CONFIG_FILE); } - + // a Predicate that returns true iff the argument is a full base unit final Predicate isFullBase = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase(); - + // print out unit counts System.out.printf( "Successfully loaded %d units with %d unit names (%d base units).%n", @@ -312,7 +348,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. * @@ -332,7 +368,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. @@ -346,10 +382,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", @@ -361,54 +397,131 @@ public final class Presenter { "Please enter a unit expression in the To: box."); return; } - - // evaluate expressions - final LinearUnitValue from; - final Unit to; - try { - from = this.database.evaluateUnitExpression(fromExpression); - } catch (final IllegalArgumentException | NoSuchElementException e) { - this.view.showErrorMessage("Parse Error", - "Could not recognize text in From entry: " + e.getMessage()); - return; + + final Optional uc; + if (toExpression.contains(";")) { + final String[] toExpressions = toExpression.split(";"); + uc = this.convertExpressionToMultiUnit(fromExpression, + toExpressions); + } else { + uc = this.convertExpressionToExpression(fromExpression, + toExpression); + } + + uc.ifPresent(xcview::showExpressionConversionOutput); + } else + throw new UnsupportedOperationException( + "This function can only be called when the view is an ExpressionConversionView"); + } + + /** + * Converts a unit expression to another expression. + * + * If an error happened, it is shown to the view and Optional.empty() is + * returned. + * + * @since 2024-08-15 + */ + private Optional convertExpressionToExpression( + String fromExpression, String toExpression) { + // evaluate expressions + final LinearUnitValue from; + final Unit to; + try { + from = this.database.evaluateUnitExpression(fromExpression); + } catch (final IllegalArgumentException | NoSuchElementException e) { + this.view.showErrorMessage("Parse Error", + "Could not recognize text in From entry: " + e.getMessage()); + return Optional.empty(); + } + try { + to = this.database.getUnitFromExpression(toExpression); + } catch (final IllegalArgumentException | NoSuchElementException e) { + this.view.showErrorMessage("Parse Error", + "Could not recognize text in To entry: " + e.getMessage()); + return Optional.empty(); + } + + // 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) { + final var toLinear = (LinearUnit) to; + uncertainValue = from.convertTo(toLinear).getValue(); + } else { + final double value = from.asUnitValue().convertTo(to).getValue(); + uncertainValue = UncertainDouble.of(value, 0); } + + final UnitConversionRecord uc = UnitConversionRecord.valueOf( + fromExpression, toExpression, "", + this.numberDisplayRule.apply(uncertainValue)); + return Optional.of(uc); + } else { + this.view.showErrorMessage("Conversion Error", + "Cannot convert between \"" + fromExpression + "\" and \"" + + toExpression + "\"."); + return Optional.empty(); + } + + } + + /** + * Convert an expression to a MultiUnit. If an error happened, it is shown to + * the view and Optional.empty() is returned. + * + * @since 2024-08-15 + */ + private Optional convertExpressionToMultiUnit( + String fromExpression, String[] toExpressions) { + final LinearUnitValue from; + try { + from = this.database.evaluateUnitExpression(fromExpression); + } catch (final IllegalArgumentException | NoSuchElementException e) { + this.view.showErrorMessage("Parse Error", + "Could not recognize text in From entry: " + e.getMessage()); + return Optional.empty(); + } + + final List toUnits = new ArrayList<>(toExpressions.length); + for (int i = 0; i < toExpressions.length; i++) { try { - to = this.database.getUnitFromExpression(toExpression); + final Unit toI = this.database + .getUnitFromExpression(toExpressions[i].trim()); + if (toI instanceof LinearUnit) { + toUnits.add((LinearUnit) toI); + } else { + this.view.showErrorMessage("Unit Type Error", + "Units separated by ';' must be linear; " + toI + + " is not."); + return Optional.empty(); + } } catch (final IllegalArgumentException | NoSuchElementException e) { this.view.showErrorMessage("Parse Error", "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) { - final var toLinear = (LinearUnit) to; - uncertainValue = from.convertTo(toLinear).getValue(); - } else { - final double value = from.asUnitValue().convertTo(to).getValue(); - uncertainValue = UncertainDouble.of(value, 0); - } - - final UnitConversionRecord uc = UnitConversionRecord.valueOf( - fromExpression, toExpression, "", - this.numberDisplayRule.apply(uncertainValue)); - xcview.showExpressionConversionOutput(uc); - } else { - this.view.showErrorMessage("Conversion Error", - "Cannot convert between \"" + fromExpression + "\" and \"" - + toExpression + "\"."); + return Optional.empty(); } - - } else - throw new UnsupportedOperationException( - "This function can only be called when the view is an ExpressionConversionView"); + } + + final List toValues; + try { + toValues = from.convertToMultiple(toUnits); + } catch (final IllegalArgumentException e) { + this.view.showErrorMessage("Unit Error", + "Invalid units separated by ';': " + e.getMessage()); + return Optional.empty(); + } + + final String toExpression = toValues.stream().map( + v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit()) + .collect(Collectors.joining(" + ")); + return Optional.of( + UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Converts from the view's input unit to its output unit. Displays an error * message if any of the required fields are invalid. @@ -422,11 +535,11 @@ public final class Presenter { public void convertUnits() { if (this.view instanceof UnitConversionView) { final UnitConversionView ucview = (UnitConversionView) this.view; - + final Optional fromUnitOptional = ucview.getFromSelection(); final Optional toUnitOptional = ucview.getToSelection(); final String inputValueString = ucview.getInputValue(); - + // extract values from optionals final String fromUnitString, toUnitString; if (fromUnitOptional.isPresent()) { @@ -443,11 +556,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 @@ -464,11 +577,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; @@ -478,18 +591,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)); @@ -497,7 +610,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 @@ -505,7 +618,7 @@ public final class Presenter { public boolean duplicatesShown() { return this.showDuplicates; } - + /** * Gets a name for this dimension using the database * @@ -520,7 +633,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 @@ -529,7 +642,7 @@ public final class Presenter { public Function getNumberDisplayRule() { return this.numberDisplayRule; } - + /** * @return the rule that is used by this presenter to convert strings into * numbers @@ -539,7 +652,7 @@ public final class Presenter { private Function getNumberParsingRule() { return this.numberParsingRule; } - + /** * @return the rule that determines whether a set of prefixes is valid * @since 2022-04-19 @@ -547,7 +660,7 @@ public final class Presenter { public Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * @return the rule that determines which units are prefixed * @since 2022-07-08 @@ -555,7 +668,7 @@ public final class Presenter { public Function, Map> getSearchRule() { return this.searchRule; } - + /** * @return a search rule that shows all single prefixes * @since 2022-07-08 @@ -564,7 +677,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 @@ -572,7 +685,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) @@ -588,7 +701,18 @@ public final class Presenter { && this.metricExceptions.contains(symbol.orElseThrow()) || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } - + + private void loadExceptionFile(Path exceptionFile) { + try (Stream lines = Files.lines(exceptionFile)) { + lines.map(Presenter::withoutComments) + .forEach(this.metricExceptions::add); + } catch (final IOException e) { + this.view.showErrorMessage("File Load Error", + "Error loading configured metric exception file \"" + + exceptionFile + "\": " + e.getLocalizedMessage()); + } + } + /** * Loads settings from the user's settings file and applies them to the * presenter. @@ -597,9 +721,10 @@ public final class Presenter { * @since 2021-12-15 */ void loadSettings(Path settingsFile) { - for (Map.Entry setting : settingsFromFile(settingsFile)) { + for (final Map.Entry setting : this + .settingsFromFile(settingsFile)) { final String value = setting.getValue(); - + switch (setting.getKey()) { // set manually to avoid the unnecessary saving of the non-manual // methods @@ -635,86 +760,12 @@ public final class Presenter { break; } } - + if (this.view.getPresenter() != null) { this.updateView(); } } - - private List> settingsFromFile(Path settingsFile) { - try (Stream 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 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 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 @@ -725,7 +776,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 @@ -739,10 +790,10 @@ public final class Presenter { final UnitConversionView ucview = (UnitConversionView) this.view; ucview.setDimensionNames(this.database.dimensionMap().keySet()); } - + this.updateView(); } - + void prefixSelected() { final Optional selectedPrefixName = this.view .getViewedPrefixName(); @@ -754,7 +805,7 @@ public final class Presenter { .ifPresent(prefix -> this.view.showPrefix(prefix.getNameSymbol(), String.valueOf(prefix.getMultiplier()))); } - + /** * Saves the presenter's current settings to the config file, creating it if * it doesn't exist. @@ -767,68 +818,35 @@ public final class Presenter { if (!Files.exists(configDir)) { try { Files.createDirectories(configDir); - } catch (IOException e) { + } catch (final 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 - */ - boolean writeSettings(Path settingsFile) { - try (BufferedWriter writer = Files.newBufferedWriter(settingsFile)) { - writer.write(String.format("number_display_rule=%s\n", - 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", - 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 void setDisplayRuleFromString(String ruleString) { + final 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 static String searchRuleToString( - Function, Map> 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 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 @@ -838,7 +856,7 @@ public final class Presenter { Function numberDisplayRule) { this.numberDisplayRule = numberDisplayRule; } - + /** * @param numberParsingRule the new rule that will be used by this presenter * to convert strings into numbers @@ -849,7 +867,7 @@ public final class Presenter { Function numberParsingRule) { this.numberParsingRule = numberParsingRule; } - + /** * @param oneWayConversionEnabled whether not one-way conversion should be * enabled @@ -860,7 +878,7 @@ public final class Presenter { this.oneWayConversionEnabled = oneWayConversionEnabled; this.updateView(); } - + /** * @param prefixRepetitionRule the rule that determines whether a set of * prefixes is valid @@ -871,7 +889,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 @@ -883,7 +901,25 @@ public final class Presenter { Function, Map> searchRule) { this.searchRule = searchRule; } - + + 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); + } + } + /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 @@ -892,7 +928,19 @@ public final class Presenter { this.showDuplicates = showDuplicateUnits; this.updateView(); } - + + private List> settingsFromFile(Path settingsFile) { + try (Stream 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; + } + } + /** * Shows a unit in the unit viewer * @@ -908,7 +956,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. @@ -924,7 +972,7 @@ public final class Presenter { : null); selectedUnit.ifPresent(this::showUnit); } - + /** * Updates the view's From and To units, if it has some * @@ -934,19 +982,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 @@ -956,7 +1004,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(), @@ -964,7 +1012,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())); @@ -972,7 +1020,7 @@ public final class Presenter { .map(Map.Entry::getKey).collect(Collectors.toSet())); } } - + /** * @param message message to add * @param args string formatting arguments for message @@ -984,4 +1032,32 @@ public final class Presenter { return new AssertionError("View Programming Error (from " + this.view + "): " + String.format(message, args)); } + + /** + * Saves the presenter's settings to the user settings file. + * + * @param settingsFile file settings should be saved to + * @since 2021-12-15 + */ + boolean writeSettings(Path settingsFile) { + try (BufferedWriter writer = Files.newBufferedWriter(settingsFile)) { + writer.write(String.format("number_display_rule=%s\n", + 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", + 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; + } + } } diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 997acc3..a71de44 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 extends AbstractSet { private final JComboBox 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 comboBox) { this.comboBox = comboBox; } - + @Override public Iterator 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 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 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 getDimensionNames() { return Collections .unmodifiableSet(new JComboBoxItemSet<>(this.dimensionSelector)); } - + @Override public String getFromExpression() { return this.fromEntry.getText(); } - + @Override public Optional getFromSelection() { return this.fromSearch.getSelectedValue(); } - + @Override public Set 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 getSelectedDimensionName() { final String selectedItem = (String) this.dimensionSelector .getSelectedItem(); return Optional.ofNullable(selectedItem); } - + @Override public String getToExpression() { return this.toEntry.getText(); } - + @Override public Optional getToSelection() { return this.toSearch.getSelectedValue(); } - + @Override public Set 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 getViewedPrefixName() { return this.prefixNameList.getSelectedValue(); } - + @Override public Optional getViewedUnitName() { return this.unitNameList.getSelectedValue(); } - + @Override public void setDimensionNames(Set dimensionNames) { this.dimensionSelector.removeAllItems(); @@ -751,45 +751,44 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.dimensionSelector.addItem(d); } } - + @Override public void setFromUnitNames(Set units) { this.fromSearch.setItems(units); } - + @Override public void setToUnitNames(Set units) { this.toSearch.setItems(units); } - + @Override public void setViewablePrefixNames(Set prefixNames) { this.prefixNameList.setItems(prefixNames); } - + @Override public void setViewableUnitNames(Set 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())); + this.expressionOutput.setText(uc.toString()); } - + @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 +796,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 43a62e6..fa64ee9 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 -- cgit v1.2.3 From b528bb32fbf3a69b57a98fedefb7656b0569f637 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 16 Aug 2024 18:09:12 -0500 Subject: Add named unit sets to unit converter --- src/main/java/sevenUnits/unit/UnitDatabase.java | 527 ++++++++++++++---------- src/main/java/sevenUnitsGUI/Presenter.java | 122 ++++-- src/main/resources/metric_exceptions.txt | 1 + src/main/resources/unitsfile.txt | 3 + 4 files changed, 391 insertions(+), 262 deletions(-) diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 0120067..58c5cac 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -123,7 +123,7 @@ public final class UnitDatabase { implements Entry { 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 prefixCoordinates = new ArrayList<>(); - + // values from the unit entry set private final Map map; private transient final List unitNames; private transient final List prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -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 next() { // get next element final Entry nextEntry = this.peek(); - + // iterate to next position this.incrementPosition(); - + return nextEntry; } - + /** * @return the next element in the iterator, without iterating over * it @@ -310,7 +310,7 @@ public final class UnitDatabase { private Entry peek() { if (!this.hasNext()) throw new NoSuchElementException("No units left!"); - + // if I have prefixes, ensure I'm not using a nonlinear unit // since all of the unprefixed stuff is done, just remove // nonlinear units @@ -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 e) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public boolean addAll( final Collection> c) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable set"); } - + @Override public boolean contains(final Object o) { // get the entry final Entry entry; - + try { // This is OK because I'm in a try-catch block, catching the // exact exception that would be thrown. @@ -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> 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> 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 prefixCoordinates = new ArrayList<>(); - + // values from the unit name set private final Map map; private transient final List unitNames; private transient final List prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -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 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 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 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 units; - + /** * The available prefixes for use. * @@ -796,12 +796,12 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map prefixes; - + // caches private transient Collection values = null; private transient Set keySet = null; private transient Set> entrySet = null; - + /** * Creates the {@code PrefixedUnitMap}. * @@ -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 remappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfAbsent(final String key, final Function mappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfPresent(final String key, final BiFunction 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> 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 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 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 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 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,12 +1064,12 @@ public final class UnitDatabase { return this.values; } } - + /** * Replacements done to *all* expression types */ private static final Map EXPRESSION_REPLACEMENTS = new HashMap<>(); - + // add data to expression replacements static { // add spaces around operators @@ -1077,7 +1077,7 @@ public final class UnitDatabase { 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 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 prefixlessUnits; - + /** * The unit prefixes in this system. * @@ -1190,7 +1190,7 @@ public final class UnitDatabase { * @since v0.1.0 */ private final Map prefixes; - + /** * The dimensions in this system. * @@ -1198,7 +1198,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map> dimensions; - + /** * A map mapping strings to units (including prefixes) * @@ -1206,7 +1206,15 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map units; - + + /** + * A map mapping strings to unit sets + * + * @since 2024-08-16 + * @since v1.0.0 + */ + private final Map> unitSets; + /** * 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 +1226,7 @@ public final class UnitDatabase { * {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))} */ private Predicate> prefixRepetitionRule; - + /** * A parser that can parse unit expressions. * @@ -1227,13 +1235,14 @@ public final class UnitDatabase { */ private final ExpressionParser unitExpressionParser = new ExpressionParser.Builder<>( this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) - .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,15 +1250,15 @@ public final class UnitDatabase { */ private final ExpressionParser 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("|", (o1, o2) -> o1.dividedBy(o2), 3) - .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 * @@ -1258,15 +1267,15 @@ public final class UnitDatabase { */ private final ExpressionParser prefixExpressionParser = new ExpressionParser.Builder<>( 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(); - + .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. * @@ -1275,14 +1284,14 @@ public final class UnitDatabase { */ private final ExpressionParser> unitDimensionParser = new ExpressionParser.Builder<>( this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0) - .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(); - + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2) + .addNumericOperator("^", (o1, o2) -> { + final int exponent = (int) Math.round(o2.value()); + return o1.toExponent(exponent); + }, 1).build(); + /** * Creates the {@code UnitsDatabase}. * @@ -1292,7 +1301,7 @@ public final class UnitDatabase { public UnitDatabase() { this(prefixes -> true); } - + /** * Creates the {@code UnitsDatabase} * @@ -1309,8 +1318,9 @@ public final class UnitDatabase { new PrefixedUnitMap(this.prefixlessUnits, this.prefixes), entry -> this.prefixRepetitionRule .test(this.getPrefixesFromName(entry.getKey()))); + this.unitSets = new HashMap<>(); } - + /** * Adds a unit dimension to the database. * @@ -1328,7 +1338,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. * @@ -1347,7 +1357,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1356,12 +1366,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("!")) { @@ -1377,11 +1387,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. * @@ -1398,7 +1408,7 @@ public final class UnitDatabase { this.prefixes.put(Objects.requireNonNull(name, "name must not be null."), namedPrefix); } - + /** * Adds a unit to the database. * @@ -1415,7 +1425,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. * @@ -1434,7 +1444,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1442,15 +1452,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("!")) { @@ -1469,6 +1479,29 @@ public final class UnitDatabase { } final String prefixName = name.substring(0, name.length() - 1); this.addPrefix(prefixName, prefix); + } else if (expression.contains(";")) { + // it's a multi-unit + final String[] parts = expression.split(";"); + final List units = new ArrayList<>(parts.length); + for (final String unitName : parts) { + final Unit unit; + try { + unit = this.getUnitFromExpression(unitName.trim()); + } catch (final NoSuchElementException e) { + System.err.printf("Parsing error on line %d:%n", lineCounter); + throw e; + } + + if (unit instanceof LinearUnit) { + units.add((LinearUnit) unit); + } else { + System.err.printf("Parsing error on line %d:%n", lineCounter); + throw new IllegalArgumentException(String.format( + "Unit '%s' is in a unit-set expression, but is not linear.", + unitName)); + } + } + this.addUnitSet(name, units); } else { // it's a unit, get the unit final Unit unit; @@ -1483,9 +1516,20 @@ public final class UnitDatabase { } } } - + /** - * Removes all units, prefixes and dimensions from this database. + * Add a unit set to the database. + * + * @param name name of unit set + * @param value unit set to add + * @since 2024-08-16 + */ + public void addUnitSet(String name, List value) { + this.unitSets.put(name, value); + } + + /** + * Removes all units, unit sets, prefixes and dimensions from this database. * * @since 2022-02-26 */ @@ -1493,8 +1537,9 @@ public final class UnitDatabase { this.dimensions.clear(); this.prefixes.clear(); this.prefixlessUnits.clear(); + this.unitSets.clear(); } - + /** * Tests if the database has a unit dimension with this name. * @@ -1506,7 +1551,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. * @@ -1518,7 +1563,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 @@ -1531,7 +1576,16 @@ public final class UnitDatabase { public boolean containsUnitName(final String name) { return this.units.containsKey(name); } - + + /** + * Returns true iff there is a unit set with this name. + * + * @since 2024-08-16 + */ + public boolean containsUnitSetName(String name) { + return this.unitSets.containsKey(name); + } + /** * @return a map mapping dimension names to dimensions * @since 2019-04-13 @@ -1540,7 +1594,7 @@ public final class UnitDatabase { public Map> dimensionMap() { return Collections.unmodifiableMap(this.dimensions); } - + /** * Evaluates a unit expression, following the same rules as * {@link #getUnitFromExpression}. @@ -1551,23 +1605,23 @@ 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 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" @@ -1580,10 +1634,10 @@ public final class UnitDatabase { + modifiedExpression.substring(i + 2); } } - + return this.unitValueExpressionParser.parseExpression(modifiedExpression); } - + /** * Gets a unit dimension from the database using its name. * @@ -1601,7 +1655,7 @@ public final class UnitDatabase { else return dimension; } - + /** * Uses the database's data to parse an expression into a unit dimension *

@@ -1624,24 +1678,24 @@ public final class UnitDatabase { public ObjectProduct getDimensionFromExpression( final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a dimension as an alias first if (this.containsDimensionName(expression)) return this.getDimension(expression); - + // force operators to have spaces String modifiedExpression = expression; - + // format expression for (final Entry replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + 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}. @@ -1660,7 +1714,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( @@ -1669,7 +1723,7 @@ public final class UnitDatabase { } else { // get a linear unit final Unit unit = this.getUnit(name); - + if (unit instanceof LinearUnit) return (LinearUnit) unit; else @@ -1677,7 +1731,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. @@ -1695,7 +1749,7 @@ public final class UnitDatabase { return LinearUnitValue.getExact(this.getLinearUnit(name), 1); } } - + /** * Gets a unit prefix from the database from its name * @@ -1716,7 +1770,7 @@ public final class UnitDatabase { return prefix; } } - + /** * Gets all of the prefixes that are on a unit name, in application order. * @@ -1727,12 +1781,12 @@ public final class UnitDatabase { List getPrefixesFromName(final String unitName) { final List 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) @@ -1742,7 +1796,7 @@ public final class UnitDatabase { longestPrefixName = name.substring(0, longestLength); } } - + // longest prefix found! final UnitPrefix prefix = this.getPrefix(longestPrefixName); prefixes.add(0, prefix); @@ -1750,7 +1804,7 @@ public final class UnitDatabase { } return prefixes; } - + /** * Gets a unit prefix from a prefix expression *

@@ -1767,24 +1821,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 replacement : EXPRESSION_REPLACEMENTS .entrySet()) { modifiedExpression = replacement.getKey().matcher(modifiedExpression) .replaceAll(replacement.getValue()); } - + return this.prefixExpressionParser.parseExpression(modifiedExpression); } - + /** * @return the prefixRepetitionRule * @since 2020-08-26 @@ -1792,7 +1846,7 @@ public final class UnitDatabase { public final Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * Gets a unit from the database from its name, looking for prefixes. * @@ -1825,9 +1879,9 @@ public final class UnitDatabase { } else return unit; } - + } - + /** * Uses the database's unit data to parse an expression into a unit *

@@ -1851,23 +1905,23 @@ 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 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++) { @@ -1879,10 +1933,23 @@ public final class UnitDatabase { + modifiedExpression.substring(i + 2); } } - + return this.unitExpressionParser.parseExpression(modifiedExpression); } - + + /** + * Get a unit set from its name, throwing a {@link NoSuchElementException} if + * there is none. + * + * @since 2024-08-16 + */ + public List getUnitSet(String name) { + final List unitSet = this.unitSets.get(name); + if (unitSet == null) + throw new NoSuchElementException("No unit set with name " + name); + return unitSet; + } + /** * Adds all dimensions from a file, using data from the database to parse * them. @@ -1922,7 +1989,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}. @@ -1938,7 +2005,7 @@ public final class UnitDatabase { } } } - + /** * Adds all units from a file, using data from the database to parse them. *

@@ -1977,7 +2044,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}. @@ -1993,7 +2060,7 @@ public final class UnitDatabase { } } } - + /** * @param includeDuplicates if false, duplicates are removed from the map * @return a map mapping prefix names to prefixes @@ -2008,7 +2075,7 @@ public final class UnitDatabase { .conditionalExistenceMap(this.prefixes, entry -> !isRemovableDuplicate(this.prefixes, entry))); } - + /** * @param prefixRepetitionRule the prefixRepetitionRule to set * @since 2020-08-26 @@ -2017,7 +2084,7 @@ public final class UnitDatabase { Predicate> prefixRepetitionRule) { this.prefixRepetitionRule = prefixRepetitionRule; } - + /** * @return a string stating the number of units, prefixes and dimensions in * the database @@ -2029,7 +2096,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. *

@@ -2061,7 +2128,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, @@ -2079,4 +2146,12 @@ public final class UnitDatabase { entry -> !isRemovableDuplicate(this.prefixlessUnits, entry))); } + + /** + * @return an unmodifiable map mapping names to unit sets + * @since 2024-08-16 + */ + public Map> unitSetMap() { + return Collections.unmodifiableMap(this.unitSets); + } } diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 8f99649..4512e01 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -46,6 +46,7 @@ import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; import sevenUnits.unit.UnitType; import sevenUnits.unit.UnitValue; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.Nameable; import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; @@ -558,17 +559,13 @@ public final class Presenter { } // convert strings to data, checking if anything is invalid - final Unit fromUnit, toUnit; + final Unit fromUnit; final UncertainDouble uncertainValue; if (this.database.containsUnitName(fromUnitString)) { fromUnit = this.database.getUnit(fromUnitString); } else throw this.viewError("Nonexistent From unit: %s", fromUnitString); - if (this.database.containsUnitName(toUnitString)) { - toUnit = this.database.getUnit(toUnitString); - } else - throw this.viewError("Nonexistent To unit: %s", toUnitString); try { uncertainValue = UncertainDouble .fromRoundedString(inputValueString); @@ -577,38 +574,81 @@ public final class Presenter { "Invalid value " + inputValueString); return; } - + if (this.database.containsUnitName(toUnitString)) { + final Unit toUnit = this.database.getUnit(toUnitString); + ucview.showUnitConversionOutput( + this.convertUnitToUnit(fromUnitString, toUnitString, + inputValueString, fromUnit, toUnit, uncertainValue)); + } else if (this.database.containsUnitSetName(toUnitString)) { + final List toMulti = this.database + .getUnitSet(toUnitString); + ucview.showUnitConversionOutput( + this.convertUnitToMulti(fromUnitString, inputValueString, + fromUnit, toMulti, uncertainValue)); + } else + throw this.viewError("Nonexistent To unit: %s", toUnitString); + } else + throw new UnsupportedOperationException( + "This function can only be called when the view is a UnitConversionView."); + } + + private UnitConversionRecord convertUnitToMulti(String fromUnitString, + String inputValueString, Unit fromUnit, List toMulti, + UncertainDouble uncertainValue) { + for (final LinearUnit toUnit : toMulti) { 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; - if (fromUnit instanceof LinearUnit && toUnit instanceof LinearUnit) { - final LinearUnit fromLinear = (LinearUnit) fromUnit; - final LinearUnit toLinear = (LinearUnit) toUnit; - 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)); - } + } + + final LinearUnitValue initValue; + if (fromUnit instanceof LinearUnit) { + final var fromLinear = (LinearUnit) fromUnit; + initValue = LinearUnitValue.of(fromLinear, uncertainValue); + } else { + initValue = UnitValue.of(fromUnit, uncertainValue.value()) + .convertToBase(NameSymbol.EMPTY); + } + + final List converted = initValue + .convertToMultiple(toMulti); + final String toExpression = converted.stream().map( + v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit()) + .collect(Collectors.joining(" + ")); + return UnitConversionRecord.valueOf(fromUnitString, toExpression, + inputValueString, ""); + + } + + private UnitConversionRecord convertUnitToUnit(String fromUnitString, + String toUnitString, String inputValueString, Unit fromUnit, + Unit toUnit, UncertainDouble uncertainValue) { + if (!fromUnit.canConvertTo(toUnit)) + throw this.viewError("Could not convert between %s and %s", fromUnit, + toUnit); - ucview.showUnitConversionOutput( - UnitConversionRecord.valueOf(fromUnitString, toUnitString, - inputValueString, outputValueString)); - } else - throw new UnsupportedOperationException( - "This function can only be called when the view is a UnitConversionView."); + // 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; + if (fromUnit instanceof LinearUnit && toUnit instanceof LinearUnit) { + final LinearUnit fromLinear = (LinearUnit) fromUnit; + final LinearUnit toLinear = (LinearUnit) toUnit; + 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)); + } + + return UnitConversionRecord.valueOf(fromUnitString, toUnitString, + inputValueString, outputValueString); } /** @@ -770,7 +810,7 @@ public final class Presenter { * @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 * unit list and imperial/USC units removed from the To unit list) - * + * * @since 2022-03-30 */ public boolean oneWayConversionEnabled() { @@ -994,6 +1034,7 @@ public final class Presenter { .entrySet().stream(); var toUnits = this.database.unitMapPrefixless(this.showDuplicates) .entrySet().stream(); + var unitSets = this.database.unitSetMap().entrySet().stream(); // filter by dimension, if one is selected if (selectedDimensionName.isPresent()) { @@ -1003,6 +1044,8 @@ public final class Presenter { u -> viewDimension.equals(u.getValue().getDimension())); toUnits = toUnits.filter( u -> viewDimension.equals(u.getValue().getDimension())); + unitSets = unitSets.filter(us -> viewDimension + .equals(us.getValue().get(0).getDimension())); } // filter by unit type, if desired @@ -1011,13 +1054,20 @@ public final class Presenter { this::isSemiMetric) != UnitType.METRIC); toUnits = toUnits.filter(u -> UnitType.getType(u.getValue(), this::isSemiMetric) != UnitType.NON_METRIC); + // unit sets are never considered metric + unitSets = unitSets + .filter(us -> this.metricExceptions.contains(us.getKey())); } // set unit names ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule) .map(Map.Entry::getKey).collect(Collectors.toSet())); - ucview.setToUnitNames(toUnits.flatMap(this::applySearchRule) - .map(Map.Entry::getKey).collect(Collectors.toSet())); + final var toUnitNames = toUnits.flatMap(this::applySearchRule) + .map(Map.Entry::getKey); + final var unitSetNames = unitSets.map(Map.Entry::getKey); + final var toNames = Stream.concat(toUnitNames, unitSetNames) + .collect(Collectors.toSet()); + ucview.setToUnitNames(toNames); } } diff --git a/src/main/resources/metric_exceptions.txt b/src/main/resources/metric_exceptions.txt index 73748c0..433dbb5 100644 --- a/src/main/resources/metric_exceptions.txt +++ b/src/main/resources/metric_exceptions.txt @@ -10,6 +10,7 @@ min minute h hour +hms d day wk diff --git a/src/main/resources/unitsfile.txt b/src/main/resources/unitsfile.txt index 543c821..ecd9bb6 100644 --- a/src/main/resources/unitsfile.txt +++ b/src/main/resources/unitsfile.txt @@ -155,6 +155,7 @@ minute 60 second min minute hour 3600 second h hour +hms hour; minute; second day 86400 second d day week 7 day @@ -189,6 +190,8 @@ yard 3 foot yd yard mile 1760 yard mi mile +ftin foot; inch +ydftin yard; foot; inch # Compressed notation kph km / hour -- cgit v1.2.3 From 72ca258d06c51e55c9fa5983bb11186250eb24b0 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 16 Aug 2024 18:13:16 -0500 Subject: Allow named unit-sets to be used as to expression --- src/main/java/sevenUnitsGUI/Presenter.java | 39 +++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 4512e01..f085dba 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -400,7 +400,10 @@ public final class Presenter { } final Optional uc; - if (toExpression.contains(";")) { + if (this.database.containsUnitSetName(toExpression)) { + uc = this.convertExpressionToNamedMultiUnit(fromExpression, + toExpression); + } else if (toExpression.contains(";")) { final String[] toExpressions = toExpression.split(";"); uc = this.convertExpressionToMultiUnit(fromExpression, toExpressions); @@ -523,6 +526,40 @@ public final class Presenter { UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } + /** + * Convert an expression to a MultiUnit with a name from the database. If an + * error happened, it is shown to the view and Optional.empty() is returned. + * + * @since 2024-08-15 + */ + private Optional convertExpressionToNamedMultiUnit( + String fromExpression, String toName) { + final LinearUnitValue from; + try { + from = this.database.evaluateUnitExpression(fromExpression); + } catch (final IllegalArgumentException | NoSuchElementException e) { + this.view.showErrorMessage("Parse Error", + "Could not recognize text in From entry: " + e.getMessage()); + return Optional.empty(); + } + + final List toUnits = this.database.getUnitSet(toName); + final List toValues; + try { + toValues = from.convertToMultiple(toUnits); + } catch (final IllegalArgumentException e) { + this.view.showErrorMessage("Unit Error", + "Invalid units separated by ';': " + e.getMessage()); + return Optional.empty(); + } + + final String toExpression = toValues.stream().map( + v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit()) + .collect(Collectors.joining(" + ")); + return Optional.of( + UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); + } + /** * Converts from the view's input unit to its output unit. Displays an error * message if any of the required fields are invalid. -- cgit v1.2.3 From cc5f9b27c403f0ebc4b5682964fb54e73f87df58 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 16 Aug 2024 18:17:50 -0500 Subject: Validate unit set non-emptiness & dimension --- src/main/java/sevenUnits/unit/UnitDatabase.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 58c5cac..05c31c4 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1501,7 +1501,13 @@ public final class UnitDatabase { unitName)); } } - this.addUnitSet(name, units); + + try { + this.addUnitSet(name, units); + } catch (final IllegalArgumentException e) { + System.err.printf("Parsing error on line %d:%n", lineCounter); + throw e; + } } else { // it's a unit, get the unit final Unit unit; @@ -1525,6 +1531,15 @@ public final class UnitDatabase { * @since 2024-08-16 */ public void addUnitSet(String name, List value) { + if (value.isEmpty()) + throw new IllegalArgumentException("Unit sets must not be empty."); + for (final LinearUnit unit : value.subList(1, value.size())) { + if (!Objects.equals(unit.getDimension(), value.get(0).getDimension())) + throw new IllegalArgumentException( + "Unit sets must be all the same dimension, " + value + + " is not."); + } + this.unitSets.put(name, value); } -- cgit v1.2.3 From 04b8aeeaa5e71e962932a73772582e783c093d50 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 16 Aug 2024 18:30:12 -0500 Subject: Print nonfinal unit set coefficients as integers These values are guaranteed to be integers, so printing them without a decimal point looks nicer and saves space. --- src/main/java/sevenUnitsGUI/Presenter.java | 46 ++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index f085dba..1244401 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -162,6 +162,16 @@ public final class Presenter { return Presenter.class.getResourceAsStream(filepath); } + /** + * Convert a linear unit value to a string, where the number is rounded to + * the nearest integer. + * + * @since 2024-08-16 + */ + private static final String linearUnitValueIntToString(LinearUnitValue uv) { + return Long.toString(Math.round(uv.getValueExact())) + " " + uv.getUnit(); + } + private static Map.Entry parseSettingLine(String line) { final int equalsIndex = line.indexOf('='); if (equalsIndex == -1) @@ -179,6 +189,8 @@ public final class Presenter { return CONFIG_FILE.getParent().resolve(pathname); } + // ====== SETTINGS ====== + private static String searchRuleToString( Function, Map> searchRule) { if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) @@ -191,8 +203,6 @@ public final class Presenter { return searchRule.toString(); } - // ====== SETTINGS ====== - /** * @return true iff a and b have any elements in common * @since 2022-04-19 @@ -519,9 +529,7 @@ public final class Presenter { return Optional.empty(); } - final String toExpression = toValues.stream().map( - v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit()) - .collect(Collectors.joining(" + ")); + final String toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } @@ -553,9 +561,7 @@ public final class Presenter { return Optional.empty(); } - final String toExpression = toValues.stream().map( - v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit()) - .collect(Collectors.joining(" + ")); + final String toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } @@ -649,9 +655,7 @@ public final class Presenter { final List converted = initValue .convertToMultiple(toMulti); - final String toExpression = converted.stream().map( - v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit()) - .collect(Collectors.joining(" + ")); + final String toExpression = this.linearUnitValueSumToString(converted); return UnitConversionRecord.valueOf(fromUnitString, toExpression, inputValueString, ""); @@ -779,6 +783,24 @@ public final class Presenter { || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } + /** + * Convert a list of LinearUnitValues that you would get from a unit-set + * conversion to a string. All but the last have their numbers rendered as + * integers, since they are always integers. The last one follows the usual + * number display rule. + * + * @since 2024-08-16 + */ + private final String linearUnitValueSumToString( + List values) { + final String integerPart = values.subList(0, values.size() - 1).stream() + .map(Presenter::linearUnitValueIntToString) + .collect(Collectors.joining(" + ")); + final LinearUnitValue last = values.get(values.size() - 1); + return integerPart + " + " + this.numberDisplayRule.apply(last.getValue()) + + " " + last.getUnit(); + } + private void loadExceptionFile(Path exceptionFile) { try (Stream lines = Files.lines(exceptionFile)) { lines.map(Presenter::withoutComments) @@ -847,7 +869,7 @@ public final class Presenter { * @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 * unit list and imperial/USC units removed from the To unit list) - * + * * @since 2022-03-30 */ public boolean oneWayConversionEnabled() { -- cgit v1.2.3 From 2c2b97f964327f14ce09b0b935a46aec77526560 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 16 Aug 2024 18:47:30 -0500 Subject: Add more information to load-success message --- src/main/java/sevenUnitsGUI/Presenter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 1244401..68f0fcb 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -353,11 +353,15 @@ public final class Presenter { // print out unit counts System.out.printf( - "Successfully loaded %d units with %d unit names (%d base units).%n", + "Successfully loaded %d unique units with %d names (%d base units), %d unique prefixes with %d names, %d unit sets, and %d named dimensions.%n", this.database.unitMapPrefixless(false).size(), this.database.unitMapPrefixless(true).size(), this.database.unitMapPrefixless(false).values().stream() - .filter(isFullBase).count()); + .filter(isFullBase).count(), + this.database.prefixMap(false).size(), + this.database.prefixMap(true).size(), + this.database.unitSetMap().size(), + this.database.dimensionMap().size()); } /** -- cgit v1.2.3 From 6f1bbc1024eae98f1815ab5f9e9cb3399f5eef9c Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 22 Aug 2024 10:12:37 -0500 Subject: Allow fractional exponents --- docs/roadmap.org | 1 - src/main/java/sevenUnits/unit/LinearUnit.java | 84 ++++++++++++--------- src/main/java/sevenUnits/unit/LinearUnitValue.java | 11 +++ src/main/java/sevenUnits/unit/UnitDatabase.java | 43 ++++------- src/main/java/sevenUnits/utils/ObjectProduct.java | 87 +++++++++++++++------- src/main/resources/unitsfile.txt | 7 ++ 6 files changed, 139 insertions(+), 94 deletions(-) diff --git a/docs/roadmap.org b/docs/roadmap.org index 5a3888f..bd0ccee 100644 --- a/docs/roadmap.org +++ b/docs/roadmap.org @@ -8,7 +8,6 @@ Feature Requirements: (It should not be required to handle features that aren't in 7Units; those definitions should be ignored with a warning) - 7Units's expression converter should support most or all of the conversion features supported by GNU Units: - Converting to sums of units (it should also be possible to do this in the unit converter with preset combinations) - - Non-integer exponents - (/Optional/) Inverse nonlinear conversion with the tilde prefix - (/Optional/) Nonlinear units should be specifiable in unit files. - /Any other feature not listed should be considered optional./ diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 6489229..a230f28 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 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. *

@@ -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. *

@@ -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 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,24 @@ public final class LinearUnit extends Unit { return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); } - + + /** + * Returns this unit to an exponent, rounding the resulting dimensions to the + * nearest integer. + * + * @since 2024-08-22 + * @see ObjectProduct#toExponentRounded + */ + public LinearUnit toExponentRounded(final double exponent) { + return valueOf(this.getBase().toExponentRounded(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. *

@@ -413,7 +425,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 +434,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 fad3eb0..db2936c 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -338,6 +338,17 @@ public final class LinearUnitValue { this.value.toExponentExact(exponent)); } + /** + * Raises this value to an exponent, rounding all dimensions to integers. + * + * @since 2024-08-22 + * @see ObjectProduct#toExponentRounded + */ + public LinearUnitValue toExponentRounded(final double exponent) { + return LinearUnitValue.of(this.unit.toExponentRounded(exponent), + this.value.toExponentExact(exponent)); + } + @Override public String toString() { return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN); diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 05c31c4..514b27d 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -44,7 +44,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import sevenUnits.utils.ConditionalExistenceCollections; -import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.ExpressionParser; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; @@ -1117,20 +1116,13 @@ public final class UnitDatabase { */ private static final LinearUnit exponentiateUnits(final LinearUnit base, final LinearUnit exponentUnit) { - // exponent function - first check if o2 is a number, - if (exponentUnit.getBase().equals(Metric.ONE.getBase())) { - // then check if it is an integer, - final double exponent = exponentUnit.getConversionFactor(); - if (DecimalComparison.equals(exponent % 1, 0)) - // then exponentiate - return base.toExponent((int) (exponent + 0.5)); - else - // not an integer - throw new UnsupportedOperationException( - "Decimal exponents are currently not supported."); - } else - // not a number - throw new IllegalArgumentException("Exponents must be numbers."); + if (!exponentUnit.getBase().equals(Metric.ONE.getBase())) + throw new IllegalArgumentException(String.format( + "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", + base, exponentUnit)); + + final double exponent = exponentUnit.getConversionFactor(); + return base.toExponentRounded(exponent); } /** @@ -1143,20 +1135,13 @@ public final class UnitDatabase { */ private static final LinearUnitValue exponentiateUnitValues( final LinearUnitValue base, final LinearUnitValue exponentValue) { - // exponent function - first check if o2 is a number, - if (exponentValue.canConvertTo(Metric.ONE)) { - // then check if it is an integer, - final double exponent = exponentValue.getValueExact(); - if (DecimalComparison.equals(exponent % 1, 0)) - // then exponentiate - return base.toExponent((int) (exponent + 0.5)); - else - // not an integer - throw new UnsupportedOperationException( - "Decimal exponents are currently not supported."); - } else - // not a number - throw new IllegalArgumentException("Exponents must be numbers."); + if (!exponentValue.canConvertTo(Metric.ONE)) + throw new IllegalArgumentException(String.format( + "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", + base, exponentValue)); + + final double exponent = exponentValue.getValueExact(); + return base.toExponentRounded(exponent); } /** diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 5a29d79..4ed70be 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -34,6 +34,12 @@ import java.util.function.Function; * @since 2019-10-16 */ public class ObjectProduct implements Nameable { + /** + * If {@link #toExponentRounded}'s rounding changes exponents by more than + * this value, a warning will be printed to standard error. + */ + private static final double ROUND_WARN_THRESHOLD = 1e-12; + /** * Returns an empty ObjectProduct of a certain type * @@ -44,7 +50,7 @@ public class ObjectProduct implements Nameable { public static final ObjectProduct empty() { return new ObjectProduct<>(new HashMap<>()); } - + /** * Gets an {@code ObjectProduct} from an object-to-integer mapping * @@ -57,7 +63,7 @@ public class ObjectProduct implements Nameable { final Map map) { return new ObjectProduct<>(new HashMap<>(map)); } - + /** * Gets an ObjectProduct that has one of the inputted argument, and nothing * else. @@ -73,7 +79,7 @@ public class ObjectProduct 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 +87,12 @@ public class ObjectProduct implements Nameable { * @since 2019-10-16 */ final Map exponents; - + /** * The object's name and symbol */ private final NameSymbol nameSymbol; - + /** * Creates a {@code ObjectProduct} without a name/symbol. * @@ -96,7 +102,7 @@ public class ObjectProduct implements Nameable { ObjectProduct(final Map exponents) { this(exponents, NameSymbol.EMPTY); } - + /** * Creates the {@code ObjectProduct}. * @@ -110,7 +116,7 @@ public class ObjectProduct implements Nameable { e -> !Integer.valueOf(0).equals(e.getValue()))); this.nameSymbol = nameSymbol; } - + /** * Calculates the quotient of two products * @@ -125,17 +131,17 @@ public class ObjectProduct implements Nameable { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) - other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + // this method relies on the use of ZeroIsNullMap @Override public boolean equals(final Object obj) { @@ -146,7 +152,7 @@ public class ObjectProduct 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 +160,7 @@ public class ObjectProduct implements Nameable { public Map exponentMap() { return this.exponents; } - + /** * @return a set of all of the base objects with non-zero exponents that make * up this dimension. @@ -163,7 +169,7 @@ public class ObjectProduct implements Nameable { */ public final Set getBaseSet() { final Set dimensions = new HashSet<>(); - + // add all dimensions with a nonzero exponent - zero exponents shouldn't // be there in the first place for (final T dimension : this.exponents.keySet()) { @@ -171,10 +177,10 @@ public class ObjectProduct implements Nameable { dimensions.add(dimension); } } - + return dimensions; } - + /** * Gets the exponent for a specific dimension. * @@ -186,17 +192,17 @@ public class ObjectProduct 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 +220,7 @@ public class ObjectProduct implements Nameable { } return oneCount == 1 && !twoOrMore; } - + /** * Multiplies this product by another * @@ -229,17 +235,17 @@ public class ObjectProduct implements Nameable { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) + other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + /** * Returns this product, but to an exponent * @@ -254,7 +260,32 @@ public class ObjectProduct implements Nameable { } return new ObjectProduct<>(map); } - + + /** + * Returns this product to an exponent, where every dimension is rounded to + * the nearest integer. + * + * This function will send a warning (via {@link System.err}) if the rounding + * significantly changes the value. + * + * @since 2024-08-22 + */ + public ObjectProduct toExponentRounded(final double exponent) { + final Map map = new HashMap<>(this.exponents); + for (final T key : this.exponents.keySet()) { + final double newExponent = this.getExponent(key) * exponent; + if (Math.abs( + newExponent - Math.round(newExponent)) > ROUND_WARN_THRESHOLD) { + System.err.printf( + "Exponent Rounding Warning: Dimension exponents must be integers, so %d ^ %g = %g was rounded to %d.\n", + this.getExponent(key), exponent, newExponent, + Math.round(newExponent)); + } + map.put(key, (int) Math.round(newExponent)); + } + return new ObjectProduct<>(map); + } + /** * Converts this product to a string using the objects' * {@link Object#toString()} method (or {@link Nameable#getShortName} if @@ -271,7 +302,7 @@ public class ObjectProduct 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 +314,7 @@ public class ObjectProduct implements Nameable { public String toString(final Function objectToString) { final List positiveStringComponents = new ArrayList<>(); final List 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 +328,15 @@ public class ObjectProduct 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/resources/unitsfile.txt b/src/main/resources/unitsfile.txt index ecd9bb6..dc33abd 100644 --- a/src/main/resources/unitsfile.txt +++ b/src/main/resources/unitsfile.txt @@ -165,6 +165,7 @@ gregorianyear 365.2425 day gregorianmonth gregorianyear / 12 # Other non-SI "metric" units +are 100 m^2 litre 0.001 m^3 liter litre l litre @@ -188,11 +189,17 @@ inch foot / 12 in inch yard 3 foot yd yard +chain 66 ft +ch chain +furlong 10 chain mile 1760 yard mi mile ftin foot; inch ydftin yard; foot; inch +# Imperial area units +acre chain * furlong + # Compressed notation kph km / hour mph mile / hour -- cgit v1.2.3 From 43e3b70003fdda6f2ccdd2471e3ee3f97ad50ada Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 22 Aug 2024 10:18:28 -0500 Subject: Add 1e-12 to unit sum parts before flooring This ensures that small errors from floor arithmetic will not, for example, cause 2 feet to be converted to 1 foot + 12 in. --- src/main/java/sevenUnits/unit/LinearUnitValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index db2936c..a8848fc 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -138,7 +138,7 @@ public final class LinearUnitValue { for (final LinearUnit unit : others.subList(0, others.size() - 1)) { final LinearUnitValue remainingInUnit = remaining.convertTo(unit); final LinearUnitValue value = getExact(unit, - Math.floor(remainingInUnit.getValueExact())); + Math.floor(remainingInUnit.getValueExact() + 1e-12)); values.add(value); remaining = remaining.minus(value); } -- cgit v1.2.3 From ea3e2bf07939926e43c7abe3fd13a7c4e93f69d1 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 22 Aug 2024 11:41:04 -0500 Subject: Show unit/dim file errors as popup Previously, any error in the unit or dimension file(s) crashes the program. Instead, 7Units now ignores any invalid lines, still parsing the correct ones, and shows a popup in case any errors happen. --- docs/roadmap.org | 3 +- .../java/sevenUnits/unit/LoadingException.java | 96 +++++++++ src/main/java/sevenUnits/unit/UnitDatabase.java | 138 ++++++------- src/main/java/sevenUnitsGUI/Presenter.java | 30 ++- .../java/sevenUnits/unit/UnitDatabaseTest.java | 221 +++++++++++---------- src/test/java/sevenUnitsGUI/TabbedViewTest.java | 28 +-- 6 files changed, 323 insertions(+), 193 deletions(-) create mode 100644 src/main/java/sevenUnits/unit/LoadingException.java diff --git a/docs/roadmap.org b/docs/roadmap.org index bd0ccee..04ea0a5 100644 --- a/docs/roadmap.org +++ b/docs/roadmap.org @@ -5,9 +5,8 @@ These requirements are subject to change. I intend to finish version 1.0.0 by [ Feature Requirements: - 7Units should be able to parse unit files from [[https://www.gnu.org/software/units/][GNU Units]], the program that inspired it. - (It should not be required to handle features that aren't in 7Units; those definitions should be ignored with a warning) - 7Units's expression converter should support most or all of the conversion features supported by GNU Units: - - Converting to sums of units (it should also be possible to do this in the unit converter with preset combinations) + - (/Mostly Done/) Converting to sums of units (it should also be possible to do this in the unit converter with preset combinations) - (/Optional/) Inverse nonlinear conversion with the tilde prefix - (/Optional/) Nonlinear units should be specifiable in unit files. - /Any other feature not listed should be considered optional./ diff --git a/src/main/java/sevenUnits/unit/LoadingException.java b/src/main/java/sevenUnits/unit/LoadingException.java new file mode 100644 index 0000000..9376ed7 --- /dev/null +++ b/src/main/java/sevenUnits/unit/LoadingException.java @@ -0,0 +1,96 @@ +/** + * Copyright (C) 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package sevenUnits.unit; + +import java.nio.file.Path; +import java.util.Optional; + +/** + * An exception that occurred when loading a file. This wrapper class adds more + * info about the error. + * + * @author Adrien Hopkins + * @since 2024-08-22 + */ +public final class LoadingException extends RuntimeException { + public static enum FileType { + UNIT, DIMENSION + } + + private static final long serialVersionUID = -8167971828216907607L; + + private final long lineNumber; + private final String line; + private final Optional file; + + private final FileType fileType; + + private final RuntimeException problem; + + public LoadingException(long lineNumber, String line, FileType fileType, + RuntimeException problem) { + super(problem); + this.lineNumber = lineNumber; + this.line = line; + this.file = Optional.empty(); + this.fileType = fileType; + this.problem = problem; + } + + public LoadingException(long lineNumber, String line, Path file, + FileType fileType, RuntimeException problem) { + super(problem); + this.lineNumber = lineNumber; + this.line = line; + this.file = Optional.of(file); + this.fileType = fileType; + this.problem = problem; + } + + public Optional file() { + return this.file; + } + + public FileType fileType() { + return this.fileType; + } + + @Override + public String getMessage() { + return this.file + .map(f -> String.format( + "Error parsing line %d of %s file '%s' (\"%s\"): %s", + this.lineNumber, this.fileType.toString().toLowerCase(), f, + this.line, this.problem)) + .orElse(String.format( + "Error parsing line %d of %s stream (\"%s\"): %s", + this.lineNumber, this.fileType.toString().toLowerCase(), + this.line, this.problem)); + } + + public String line() { + return this.line; + } + + public long lineNumber() { + return this.lineNumber; + } + + public RuntimeException problem() { + return this.problem; + } +} diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 514b27d..dc81aca 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1364,16 +1364,7 @@ public final class UnitDatabase { throw new IllegalArgumentException(String.format( "! used but no dimension found (line %d).", lineCounter)); } else { - // it's a unit, get the unit - final ObjectProduct dimension; - try { - dimension = this.getDimensionFromExpression(expression); - } catch (final IllegalArgumentException | NoSuchElementException e) { - System.err.printf("Parsing error on line %d:%n", lineCounter); - throw e; - } - - this.addDimension(name, dimension); + this.addDimension(name, this.getDimensionFromExpression(expression)); } } @@ -1454,56 +1445,15 @@ public final class UnitDatabase { .format("! used but no unit found (line %d).", lineCounter)); } else { if (name.endsWith("-")) { - final UnitPrefix prefix; - try { - prefix = this.getPrefixFromExpression(expression); - } catch (final IllegalArgumentException - | NoSuchElementException e) { - System.err.printf("Parsing error on line %d:%n", lineCounter); - throw e; - } final String prefixName = name.substring(0, name.length() - 1); - this.addPrefix(prefixName, prefix); + this.addPrefix(prefixName, + this.getPrefixFromExpression(expression)); } else if (expression.contains(";")) { // it's a multi-unit - final String[] parts = expression.split(";"); - final List units = new ArrayList<>(parts.length); - for (final String unitName : parts) { - final Unit unit; - try { - unit = this.getUnitFromExpression(unitName.trim()); - } catch (final NoSuchElementException e) { - System.err.printf("Parsing error on line %d:%n", lineCounter); - throw e; - } - - if (unit instanceof LinearUnit) { - units.add((LinearUnit) unit); - } else { - System.err.printf("Parsing error on line %d:%n", lineCounter); - throw new IllegalArgumentException(String.format( - "Unit '%s' is in a unit-set expression, but is not linear.", - unitName)); - } - } - - try { - this.addUnitSet(name, units); - } catch (final IllegalArgumentException e) { - System.err.printf("Parsing error on line %d:%n", lineCounter); - throw e; - } + this.addUnitSet(name, this.getUnitSetFromExpression(expression)); } else { // it's a unit, get the unit - final Unit unit; - try { - unit = this.getUnitFromExpression(expression); - } catch (final IllegalArgumentException - | NoSuchElementException e) { - System.err.printf("Parsing error on line %d:%n", lineCounter); - throw e; - } - this.addUnit(name, unit); + this.addUnit(name, this.getUnitFromExpression(expression)); } } } @@ -1950,6 +1900,27 @@ public final class UnitDatabase { return unitSet; } + /** + * Parses a semicolon-separated expression to get the unit set being used. + * + * @since 2024-08-22 + */ + private List getUnitSetFromExpression(String expression) { + final String[] parts = expression.split(";"); + final List units = new ArrayList<>(parts.length); + for (final String unitName : parts) { + final Unit unit = this.getUnitFromExpression(unitName.trim()); + + if (unit instanceof LinearUnit) { + units.add((LinearUnit) unit); + } else + throw new IllegalArgumentException(String.format( + "Unit '%s' is in a unit-set expression, but is not linear.", + unitName)); + } + return units; + } + /** * Adds all dimensions from a file, using data from the database to parse * them. @@ -1970,24 +1941,30 @@ public final class UnitDatabase { * * * @param file file to read - * @throws IllegalArgumentException if the file cannot be parsed, found or - * read - * @throws NullPointerException if file is null + * @throws NullPointerException if file is null + * @returns list of errors that happened when loading file * @since 2019-01-13 * @since v0.1.0 */ - public void loadDimensionFile(final Path file) { + public List loadDimensionFile(final Path file) { Objects.requireNonNull(file, "file must not be null."); + final List errors = new ArrayList<>(); try { long lineCounter = 0; for (final String line : Files.readAllLines(file)) { - this.addDimensionFromLine(line, ++lineCounter); + try { + this.addDimensionFromLine(line, ++lineCounter); + } catch (IllegalArgumentException | NoSuchElementException e) { + errors.add(new LoadingException(lineCounter, line, file, + LoadingException.FileType.DIMENSION, e)); + } } } catch (final FileNotFoundException e) { throw new IllegalArgumentException("Could not find file " + file, e); } catch (final IOException e) { throw new IllegalArgumentException("Could not read file " + file, e); } + return errors; } /** @@ -1997,13 +1974,22 @@ public final class UnitDatabase { * @param stream stream to load from * @since 2021-03-27 */ - public void loadDimensionsFromStream(final InputStream stream) { + public List loadDimensionsFromStream( + final InputStream stream) { + final List errors = new ArrayList<>(); try (final Scanner scanner = new Scanner(stream)) { long lineCounter = 0; while (scanner.hasNextLine()) { - this.addDimensionFromLine(scanner.nextLine(), ++lineCounter); + final String line = scanner.nextLine(); + try { + this.addDimensionFromLine(line, ++lineCounter); + } catch (IllegalArgumentException | NoSuchElementException e) { + errors.add(new LoadingException(lineCounter, line, + LoadingException.FileType.DIMENSION, e)); + } } } + return errors; } /** @@ -2025,24 +2011,30 @@ public final class UnitDatabase { * * * @param file file to read - * @throws IllegalArgumentException if the file cannot be parsed, found or - * read - * @throws NullPointerException if file is null + * @throws NullPointerException if file is null + * @returns list of errors that happened when loading file * @since 2019-01-13 * @since v0.1.0 */ - public void loadUnitsFile(final Path file) { + public List loadUnitsFile(final Path file) { Objects.requireNonNull(file, "file must not be null."); + final List errors = new ArrayList<>(); try { long lineCounter = 0; for (final String line : Files.readAllLines(file)) { - this.addUnitOrPrefixFromLine(line, ++lineCounter); + try { + this.addUnitOrPrefixFromLine(line, ++lineCounter); + } catch (IllegalArgumentException | NoSuchElementException e) { + errors.add(new LoadingException(lineCounter, line, file, + LoadingException.FileType.UNIT, e)); + } } } catch (final FileNotFoundException e) { throw new IllegalArgumentException("Could not find file " + file, e); } catch (final IOException e) { throw new IllegalArgumentException("Could not read file " + file, e); } + return errors; } /** @@ -2052,13 +2044,21 @@ public final class UnitDatabase { * @param stream stream to load from * @since 2021-03-27 */ - public void loadUnitsFromStream(InputStream stream) { + public List loadUnitsFromStream(InputStream stream) { + final List errors = new ArrayList<>(); try (final Scanner scanner = new Scanner(stream)) { long lineCounter = 0; while (scanner.hasNextLine()) { - this.addUnitOrPrefixFromLine(scanner.nextLine(), ++lineCounter); + final String line = scanner.nextLine(); + try { + this.addUnitOrPrefixFromLine(line, ++lineCounter); + } catch (IllegalArgumentException | NoSuchElementException e) { + errors.add(new LoadingException(lineCounter, line, + LoadingException.FileType.UNIT, e)); + } } } + return errors; } /** diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 68f0fcb..4ff2d65 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -40,6 +40,7 @@ import sevenUnits.unit.BaseUnit; import sevenUnits.unit.BritishImperial; import sevenUnits.unit.LinearUnit; import sevenUnits.unit.LinearUnitValue; +import sevenUnits.unit.LoadingException; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; @@ -311,7 +312,7 @@ public final class Presenter { // load units and prefixes try (final InputStream units = inputStream(DEFAULT_UNITS_FILEPATH)) { - this.database.loadUnitsFromStream(units); + this.handleLoadErrors(this.database.loadUnitsFromStream(units)); } catch (final IOException e) { throw new AssertionError("Loading of unitsfile.txt failed.", e); } @@ -319,7 +320,8 @@ public final class Presenter { // load dimensions try (final InputStream dimensions = inputStream( DEFAULT_DIMENSIONS_FILEPATH)) { - this.database.loadDimensionsFromStream(dimensions); + this.handleLoadErrors( + this.database.loadDimensionsFromStream(dimensions)); } catch (final IOException e) { throw new AssertionError("Loading of dimensionfile.txt failed.", e); } @@ -771,6 +773,24 @@ public final class Presenter { return this.view; } + /** + * Accepts a list of errors. If that list is non-empty, prints an error + * message and alerts the user. + * + * @since 2024-08-22 + */ + private void handleLoadErrors(List errors) { + if (!errors.isEmpty()) { + final String errorMessage = String.format( + "%d error(s) happened while loading file:\n%s\n", errors.size(), + errors.stream().map(Throwable::getMessage) + .collect(Collectors.joining("\n"))); + System.err.print(errorMessage); + this.view.showErrorMessage(errors.size() + "Loading Error(s)", + errorMessage); + } + } + /** * @return whether or not the provided unit is semi-metric (i.e. an * exception) @@ -832,13 +852,15 @@ public final class Presenter { // set manually to avoid the unnecessary saving of the non-manual // methods case "custom_dimension_file": - this.database.loadDimensionFile(pathFromConfig(value)); + this.handleLoadErrors( + this.database.loadDimensionFile(pathFromConfig(value))); break; case "custom_exception_file": this.loadExceptionFile(pathFromConfig(value)); break; case "custom_unit_file": - this.database.loadUnitsFile(pathFromConfig(value)); + this.handleLoadErrors( + this.database.loadUnitsFile(pathFromConfig(value))); break; case "number_display_rule": this.setDisplayRuleFromString(value); diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 9d650f0..e7f3ccf 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -53,9 +54,9 @@ import sevenUnits.utils.UncertainDouble; class UnitDatabaseTest { private static final class SimpleEntry implements Map.Entry { private final K key; - + private V value; - + /** * * @since 2021-10-07 @@ -64,7 +65,7 @@ class UnitDatabaseTest { this.key = key; this.value = value; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -75,23 +76,23 @@ class UnitDatabaseTest { return Objects.equals(this.key, other.getKey()) && Objects.equals(this.value, other.getValue()); } - + @Override public K getKey() { return this.key; } - + @Override public V getValue() { return this.value; } - + @Override public int hashCode() { return (this.key == null ? 0 : this.key.hashCode()) ^ (this.value == null ? 0 : this.value.hashCode()); } - + @Override public V setValue(V value) { final V oldValue = this.value; @@ -99,20 +100,20 @@ class UnitDatabaseTest { return oldValue; } } - + // some linear units and one nonlinear private static final Unit U = Metric.METRE; private static final Unit V = Metric.KILOGRAM; - + private static final Unit W = Metric.SECOND; // used for testing expressions // J = U^2 * V / W^2 private static final LinearUnit J = Metric.KILOGRAM .times(Metric.METRE.toExponent(2)) .dividedBy(Metric.SECOND.toExponent(2)); - + private static final LinearUnit K = Metric.KELVIN; - + private static final Unit NONLINEAR = Unit.fromConversionFunctions( Metric.METRE.getBase(), o -> o + 1, o -> o - 1); // make the prefix values prime so I can tell which multiplications were made @@ -123,9 +124,9 @@ class UnitDatabaseTest { private static final UnitPrefix C = UnitPrefix.valueOf(5) .withName(NameSymbol.ofName("C")); private static final UnitPrefix AB = UnitPrefix.valueOf(7); - + private static final UnitPrefix BC = UnitPrefix.valueOf(11); - + /** * Gets a map entry. * @@ -139,41 +140,47 @@ class UnitDatabaseTest { private static Map.Entry entry(K key, V value) { return new SimpleEntry<>(key, value); } - + /** * Loads the dimensionfile at src/test/resources/[path] to the database * {@code loadTo}. * * @param loadTo database to load to * @param path path of file to load + * @return exceptions returned by file loading * @since 2021-10-04 */ - private static void loadDimensionFile(UnitDatabase loadTo, String path) { + private static List loadDimensionFile(UnitDatabase loadTo, + String path) { try (final InputStream testFile = UnitDatabaseTest.class .getResourceAsStream(path)) { - loadTo.loadDimensionsFromStream(testFile); + return loadTo.loadDimensionsFromStream(testFile); } catch (final IOException e) { fail(e.getClass() + " occurred upon loading file \"" + path + "\"."); + return Collections.emptyList(); } } - + /** * Loads the unitfile at src/test/resources/[path] to the database * {@code loadTo}. * * @param loadTo database to load to * @param path path of file to load + * @return exceptions returned by file loading * @since 2021-09-22 */ - private static void loadUnitsFile(UnitDatabase loadTo, String path) { + private static List loadUnitsFile(UnitDatabase loadTo, + String path) { try (final InputStream testFile = UnitDatabaseTest.class .getResourceAsStream(path)) { - loadTo.loadUnitsFromStream(testFile); + return loadTo.loadUnitsFromStream(testFile); } catch (final IOException e) { fail(e.getClass() + " occurred upon loading file \"" + path + "\"."); + return Collections.emptyList(); } } - + /** * A test for the {@link UnitDatabase#evaluateUnitExpression(String)} * function. Simple because the expression parser has its own test. @@ -183,26 +190,26 @@ class UnitDatabaseTest { @Test public void testEvaluateExpression() { final UnitDatabase database = new UnitDatabase(); - + database.addUnit("J", J); database.addUnit("K", K); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + final LinearUnitValue expected = LinearUnitValue.of(J, UncertainDouble.of(12, Math.sqrt(14.625))); // note: units are exact, each number has an uncertainty of 1 final LinearUnitValue actual = database .evaluateUnitExpression("J + (2 * 3) J + (20 / 4) J"); assertEquals(expected, actual); - + // check that negation works properly assertEquals(2, database.evaluateUnitExpression("J - -1 * J").getValueExact()); } - + /** * Test for {@link UnitDatabase#getUnit}, {@link UnitDatabase#getLinearUnit} * and {@link UnitDatabase#getLinearUnitValue}. @@ -212,14 +219,14 @@ class UnitDatabaseTest { @Test public void testGetUnit() { final UnitDatabase database = new UnitDatabase(); - + database.addUnit("m", Metric.METRE); database.addUnit("meter", Metric.METRE); database.addUnit("metre", Metric.METRE); database.addUnit("badname", Metric.METRE); database.addUnit("K", Metric.KELVIN); database.addUnit("degC", Metric.CELSIUS); - + // ensure getUnit returns units, regardless of whether the name is one of // the unit's names assertEquals(Metric.METRE, database.getUnit("m")); @@ -228,14 +235,14 @@ class UnitDatabaseTest { assertEquals(Metric.METRE, database.getUnit("badname")); assertThrows(NoSuchElementException.class, () -> database.getUnit("blabla")); - + assertEquals(Metric.KELVIN, database.getLinearUnit("K")); assertThrows(IllegalArgumentException.class, () -> database.getLinearUnit("degC")); assertEquals(Metric.KELVIN.times(373.15), database.getLinearUnit("degC(100)")); } - + /** * Confirms that operations that shouldn't function for infinite databases * throw an {@code IllegalStateException}. @@ -247,21 +254,21 @@ class UnitDatabaseTest { public void testInfiniteSetExceptions() { // load units final UnitDatabase infiniteDatabase = new UnitDatabase(); - + infiniteDatabase.addUnit("J", J); infiniteDatabase.addUnit("K", K); - + infiniteDatabase.addPrefix("A", A); infiniteDatabase.addPrefix("B", B); infiniteDatabase.addPrefix("C", C); - + final Set> entrySet = infiniteDatabase.unitMap() .entrySet(); final Set keySet = infiniteDatabase.unitMap().keySet(); assertThrows(IllegalStateException.class, () -> entrySet.toArray()); assertThrows(IllegalStateException.class, () -> keySet.toArray()); } - + /** * A bunch of tests for invalid dimension files * @@ -277,12 +284,13 @@ class UnitDatabaseTest { database.addDimension("TIME", Metric.Dimensions.TIME); final String filename = String.format("/test-dimensionfile-invalid%d.txt", num); - final RuntimeException e = assertThrows(RuntimeException.class, - () -> loadDimensionFile(database, filename)); + final List errs = loadDimensionFile(database, filename); + assertFalse(errs.isEmpty(), "no error from invalid file " + filename); + final RuntimeException e = errs.get(0).problem(); assertTrue(e instanceof IllegalArgumentException || e instanceof NoSuchElementException); } - + /** * A bunch of tests for invalid unit files * @@ -295,12 +303,13 @@ class UnitDatabaseTest { final UnitDatabase database = new UnitDatabase(); final String filename = String.format("/test-unitsfile-invalid%d.txt", num); - final RuntimeException e = assertThrows(RuntimeException.class, - () -> loadUnitsFile(database, filename)); + final List errs = loadUnitsFile(database, filename); + assertFalse(errs.isEmpty(), "no error from invalid file " + filename); + final RuntimeException e = errs.get(0).problem(); assertTrue(e instanceof IllegalArgumentException || e instanceof NoSuchElementException); } - + /** * Tests loading a valid dimension-file with some derived dimensions. * @@ -312,13 +321,13 @@ class UnitDatabaseTest { database.addDimension("LENGTH", Metric.Dimensions.LENGTH); database.addDimension("MASS", Metric.Dimensions.MASS); database.addDimension("TIME", Metric.Dimensions.TIME); - + loadDimensionFile(database, "/test-dimensionfile-valid1.txt"); assertEquals(Metric.Dimensions.ENERGY, database.getDimension("ENERGY")); assertEquals(Metric.Dimensions.POWER, database.getDimension("POWER")); - + } - + /** * Tests loading a valid unitfile with some prefixes and no units. * @@ -327,13 +336,13 @@ class UnitDatabaseTest { @Test public void testLoadingValidPrefixes() { final UnitDatabase database = new UnitDatabase(); - + loadUnitsFile(database, "/test-unitsfile-valid2.txt"); assertEquals(7, database.getPrefix("A").getMultiplier()); assertEquals(11, database.getPrefix("B").getMultiplier()); assertEquals(13, database.getPrefix("C").getMultiplier()); } - + /** * Tests loading a valid unitfile with some units and preloaded prefixes * @@ -342,43 +351,43 @@ class UnitDatabaseTest { @Test public void testLoadingValidUnits() { final UnitDatabase database = new UnitDatabase(); - + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); database.addUnit("fj", J.times(5)); database.addUnit("ej", J.times(8)); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + loadUnitsFile(database, "/test-unitsfile-valid1.txt"); - + final Unit expected1 = ((LinearUnit) U).withPrefix(A).withPrefix(B) .withPrefix(C); final Unit actual1 = database.getUnit("test1"); assertEquals(expected1, actual1); - + final Unit expected2 = ((LinearUnit) W).withPrefix(B) .times(((LinearUnit) V).withPrefix(C)); final Unit actual2 = database.getUnit("test2"); assertEquals(expected2, actual2); - + final Unit expected3 = ((LinearUnit) U) .times(A.getMultiplier() + C.getMultiplier() - B.getMultiplier()); final Unit actual3 = database.getUnit("test3"); assertEquals(expected3, actual3); - + final UnitValue expected4 = UnitValue.of(U, 1); final UnitValue actual4 = database .evaluateUnitExpression("-5 * U + -3 * U + 12 * U - 3 * U") .asUnitValue(); assertEquals(expected4, actual4); - + assertTrue(System.err.toString().length() > 0); } - + /** * Tests the iterator of the prefixless unit map. These tests are simple, as * the unit map iterator is simple. @@ -388,16 +397,16 @@ class UnitDatabaseTest { @Test public void testPrefixedUnitMapIterator() { final UnitDatabase database1 = new UnitDatabase(); - + database1.addUnit("U", U); database1.addUnit("V", V); database1.addUnit("W", W); - + final Map map1 = database1.unitMap(); final Iterator keyIterator1 = map1.keySet().iterator(); final Iterator> entryIterator1 = map1.entrySet() .iterator(); - + final Set expectedKeys = Set.of("U", "V", "W"); final Set actualKeys = new HashSet<>(); while (keyIterator1.hasNext()) { @@ -405,7 +414,7 @@ class UnitDatabaseTest { } assertEquals(expectedKeys, actualKeys); assertEquals(expectedKeys, map1.keySet()); - + final Set> expectedEntries = Set.of(entry("U", U), entry("V", V), entry("W", W)); final Set> actualEntries = new HashSet<>(); @@ -415,7 +424,7 @@ class UnitDatabaseTest { assertEquals(expectedEntries, actualEntries); assertEquals(expectedEntries, map1.entrySet()); } - + /** * Test that prefixes correctly apply to units. * @@ -425,28 +434,28 @@ class UnitDatabaseTest { @Test public void testPrefixes() { final UnitDatabase database = new UnitDatabase(); - + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + // test the getPrefixesFromName method final List expected = Arrays.asList(C, B, A); assertEquals(expected, database.getPrefixesFromName("ABCU")); - + // get the product final Unit abcuNonlinear = database.getUnit("ABCU"); assert abcuNonlinear instanceof LinearUnit; - + final LinearUnit abcu = (LinearUnit) abcuNonlinear; assertEquals(A.getMultiplier() * B.getMultiplier() * C.getMultiplier(), abcu.getConversionFactor(), 1e-15); } - + /** * Tests the functionnalites of the prefixless unit map. * @@ -462,19 +471,19 @@ class UnitDatabaseTest { final UnitDatabase database = new UnitDatabase(); final Map prefixlessUnits = database .unitMapPrefixless(true); - + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); - + // this should work because the map should be an auto-updating view assertTrue(prefixlessUnits.containsKey("U")); assertFalse(prefixlessUnits.containsKey("Z")); - + assertTrue(prefixlessUnits.containsValue(U)); assertFalse(prefixlessUnits.containsValue(NONLINEAR)); } - + /** * Tests that the database correctly stores and retrieves units, ignoring * prefixes. @@ -485,18 +494,18 @@ class UnitDatabaseTest { @Test public void testPrefixlessUnits() { final UnitDatabase database = new UnitDatabase(); - + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); - + assertTrue(database.containsUnitName("U")); assertFalse(database.containsUnitName("Z")); - + assertEquals(U, database.getUnit("U")); assertThrows(NoSuchElementException.class, () -> database.getUnit("Z")); } - + @Test public void testRemovableDuplicates() { final Map unitMap = new HashMap<>(); @@ -504,7 +513,7 @@ class UnitDatabaseTest { unitMap.put("metre", Metric.METRE); unitMap.put("m", Metric.METRE); unitMap.put("second", Metric.SECOND); - + assertTrue(UnitDatabase.isRemovableDuplicate(unitMap, entry("m", Metric.METRE))); assertTrue(UnitDatabase.isRemovableDuplicate(unitMap, @@ -514,28 +523,28 @@ class UnitDatabaseTest { assertFalse(UnitDatabase.isRemovableDuplicate(unitMap, entry("second", Metric.SECOND))); } - + @Test public void testToString() { final UnitDatabase database = new UnitDatabase(); - + database.addUnit("J", J); database.addUnit("K", J); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + if ("Unit Database with 1 units, 3 unit prefixes and 0 dimensions" .equals(database.toString())) { fail("Database counts by number of units, not number of unit names."); } - + assertEquals( "Unit Database with 2 units, 3 unit prefixes and 0 dimensions", database.toString()); } - + /** * Test that unit expressions return the correct value. * @@ -546,37 +555,37 @@ class UnitDatabaseTest { public void testUnitExpressions() { // load units final UnitDatabase database = new UnitDatabase(); - + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); database.addUnit("fj", J.times(5)); database.addUnit("ej", J.times(8)); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + // first test - test prefixes and operations final Unit expected1 = J.withPrefix(A).withPrefix(B).withPrefix(C) .withPrefix(C); final Unit actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W"); - + assertEquals(expected1, actual1); - + // second test - test addition and subtraction final Unit expected2 = J.times(58); final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej"); - + assertEquals(expected2, actual2); - + // test incorrect expressions assertThrows(IllegalArgumentException.class, () -> database.getUnitFromExpression("U + V")); assertThrows(IllegalArgumentException.class, () -> database.getUnitFromExpression("U - V")); } - + /** * Tests both the unit name iterator and the name-unit entry iterator * @@ -587,25 +596,25 @@ class UnitDatabaseTest { public void testUnitIterator() { // load units final UnitDatabase database = new UnitDatabase(); - + database.addUnit("J", J); database.addUnit("K", K); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + final int NUM_UNITS = database.unitMapPrefixless(true).size(); final int NUM_PREFIXES = database.prefixMap(true).size(); - + final Iterator nameIterator = database.unitMap().keySet() .iterator(); final Iterator> entryIterator = database.unitMap() .entrySet().iterator(); - + int expectedLength = 1; int unitsWithThisLengthSoFar = 0; - + // loop 1000 times for (int i = 0; i < 1000; i++) { // expected length of next @@ -614,31 +623,31 @@ class UnitDatabaseTest { expectedLength++; unitsWithThisLengthSoFar = 0; } - + // test that stuff is valid final String nextName = nameIterator.next(); final Unit nextUnit = database.getUnit(nextName); final Entry nextEntry = entryIterator.next(); - + assertEquals(expectedLength, nextName.length()); assertEquals(nextName, nextEntry.getKey()); assertEquals(nextUnit, nextEntry.getValue()); - + unitsWithThisLengthSoFar++; } - + // test toString for consistency final String entryIteratorString = entryIterator.toString(); for (int i = 0; i < 3; i++) { assertEquals(entryIteratorString, entryIterator.toString()); } - + final String nameIteratorString = nameIterator.toString(); for (int i = 0; i < 3; i++) { assertEquals(nameIteratorString, nameIterator.toString()); } } - + /** * Determine, given a unit name that could mean multiple things, which * meaning is chosen. @@ -654,28 +663,28 @@ class UnitDatabaseTest { public void testUnitPrefixCombinations() { // load units final UnitDatabase database = new UnitDatabase(); - + database.addUnit("J", J); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); database.addPrefix("AB", AB); database.addPrefix("BC", BC); - + // test 1 - AB-C-J vs A-BC-J vs A-B-C-J final Unit expected1 = J.withPrefix(AB).withPrefix(C); final Unit actual1 = database.getUnit("ABCJ"); - + assertEquals(expected1, actual1); - + // test 2 - ABC-J vs AB-CJ vs AB-C-J database.addUnit("CJ", J.times(13)); database.addPrefix("ABC", UnitPrefix.valueOf(17)); - + final Unit expected2 = J.times(17); final Unit actual2 = database.getUnit("ABCJ"); - + assertEquals(expected2, actual2); } } diff --git a/src/test/java/sevenUnitsGUI/TabbedViewTest.java b/src/test/java/sevenUnitsGUI/TabbedViewTest.java index 165718f..017e9ea 100644 --- a/src/test/java/sevenUnitsGUI/TabbedViewTest.java +++ b/src/test/java/sevenUnitsGUI/TabbedViewTest.java @@ -18,14 +18,18 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.concurrent.TimeUnit; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; /** - * Test for the TabbedView + * Test for the TabbedView. * * @since v0.4.0 * @since 2022-07-17 */ +@Timeout(value = 10, unit = TimeUnit.SECONDS) class TabbedViewTest { /** * @return a view with all settings set to standard values @@ -36,17 +40,17 @@ class TabbedViewTest { private static final TabbedView setupView() { final var view = new TabbedView(); final var presenter = view.getPresenter(); - + presenter.setNumberDisplayRule(StandardDisplayRules.uncertaintyBased()); presenter.setPrefixRepetitionRule( DefaultPrefixRepetitionRule.NO_RESTRICTION); presenter.setSearchRule(PrefixSearchRule.COMMON_PREFIXES); presenter.setOneWayConversionEnabled(false); presenter.setShowDuplicates(true); - + return view; } - + /** * Simulates an expression conversion operation, and ensures it works * properly. @@ -57,18 +61,18 @@ class TabbedViewTest { @Test void testExpressionConversion() { final var view = setupView(); - + // prepare for unit conversion view.masterPane.setSelectedIndex(1); view.fromEntry.setText("250.0 inch"); view.toEntry.setText("metre"); - + view.convertExpressionButton.doClick(); - + // check result of conversion assertEquals("250.0 inch = 6.350 metre", view.expressionOutput.getText()); } - + /** * Simulates a unit conversion operation, and ensures it works properly. * @@ -78,18 +82,18 @@ class TabbedViewTest { @Test void testUnitConversion() { final var view = setupView(); - + // prepare for unit conversion view.masterPane.setSelectedIndex(0); view.dimensionSelector.setSelectedItem("Length"); view.fromSearch.getSearchList().setSelectedValue("inch", true); view.toSearch.getSearchList().setSelectedValue("metre", true); view.valueInput.setText("250.0"); - + view.convertUnitButton.doClick(); - + // check result of conversion assertEquals("250.0 inch = 6.350 metre", view.unitOutput.getText()); } - + } -- cgit v1.2.3 From 6feabb35336fa3ca1faaff203f50086dc018b5ce Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 22 Aug 2024 11:49:52 -0500 Subject: Update changelog --- CHANGELOG.org | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.org b/CHANGELOG.org index ad2f09b..344b4a0 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -3,6 +3,13 @@ All notable changes in this project will be shown in this file. ** Unreleased *** Added - *Allowed conversion to a sum of units (e.g. 4/3 ft \rightarrow 1 ft + 4 in).* +- *Allowed exponents on units to be non-integer numbers.* + The resulting exponents are rounded to the nearest integer, and the user is warned if this rounding changes the value by more than normal floating-point error. +- Add more information to the loading-success message. +*** Changed +- *Errors in unit/dimension files are shown in popups, rather than crashing the program.* +*** Fixed +- Fixed encoding of \pm character in values with uncertainty. ** v0.5.0 - [2024-03-24 Sun] *** Added - *Added specifications for all types data files used by 7Units.* -- cgit v1.2.3 From b0242b898653f2dc23f2187deec9db0bb652751d Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 22 Aug 2024 12:19:30 -0500 Subject: Add loading counts to About tab --- CHANGELOG.org | 2 +- src/main/java/sevenUnitsGUI/Presenter.java | 88 ++++++++++++++++++++++------- src/main/java/sevenUnitsGUI/TabbedView.java | 2 +- src/main/resources/about.txt | 4 ++ 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.org b/CHANGELOG.org index 344b4a0..78bb9a1 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -5,7 +5,7 @@ All notable changes in this project will be shown in this file. - *Allowed conversion to a sum of units (e.g. 4/3 ft \rightarrow 1 ft + 4 in).* - *Allowed exponents on units to be non-integer numbers.* The resulting exponents are rounded to the nearest integer, and the user is warned if this rounding changes the value by more than normal floating-point error. -- Add more information to the loading-success message. +- Added more information to the loading-success message, and added it to the about tab. *** Changed - *Errors in unit/dimension files are shown in popups, rather than crashing the program.* *** Fixed diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 4ff2d65..c46ee53 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -74,6 +74,9 @@ public final class Presenter { 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"; + /** A Predicate that returns true iff the argument is a full base unit */ + private static final Predicate IS_FULL_BASE = unit -> unit instanceof LinearUnit + && ((LinearUnit) unit).isBase(); /** * Adds default units and dimensions to a database. @@ -119,13 +122,17 @@ public final class Presenter { } /** - * @return text in About file - * @since 2022-02-19 + * Determines where to wrap {@code toWrap} with a max line length of + * {@code maxLineLength}. If no good spot is found, returns -1. + * + * @since 2024-08-22 */ - public static final String getAboutText() { - return Presenter.getLinesFromResource("/about.txt").stream() - .map(Presenter::withoutComments).collect(Collectors.joining("\n")) - .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()); + private static final int findLineSplit(String toWrap, int maxLineLength) { + for (int i = maxLineLength - 1; i >= 0; i--) { + if (Character.isWhitespace(toWrap.charAt(i))) + return i; + } + return -1; } /** @@ -241,6 +248,30 @@ public final class Presenter { return index == -1 ? line : line.substring(0, index); } + /** + * Wraps a string, ensuring no line is longer than {@code maxLineLength}. + * + * @since 2024-08-22 + */ + private static final String wrapString(String toWrap, int maxLineLength) { + final StringBuilder wrapped = new StringBuilder(toWrap.length()); + String remaining = toWrap; + while (remaining.length() > maxLineLength) { + final int spot = findLineSplit(toWrap, maxLineLength); + if (spot == -1) { + wrapped.append(remaining.substring(0, maxLineLength)); + wrapped.append("-\n"); + remaining = remaining.substring(maxLineLength).stripLeading(); + } else { + wrapped.append(remaining.substring(0, spot)); + wrapped.append("\n"); + remaining = remaining.substring(spot + 1).stripLeading(); + } + } + wrapped.append(remaining); + return wrapped.toString(); + } + /** * The view that this presenter communicates with */ @@ -349,21 +380,8 @@ public final class Presenter { this.loadSettings(CONFIG_FILE); } - // a Predicate that returns true iff the argument is a full base unit - final Predicate isFullBase = unit -> unit instanceof LinearUnit - && ((LinearUnit) unit).isBase(); - // print out unit counts - System.out.printf( - "Successfully loaded %d unique units with %d names (%d base units), %d unique prefixes with %d names, %d unit sets, and %d named dimensions.%n", - this.database.unitMapPrefixless(false).size(), - this.database.unitMapPrefixless(true).size(), - this.database.unitMapPrefixless(false).values().stream() - .filter(isFullBase).count(), - this.database.prefixMap(false).size(), - this.database.prefixMap(true).size(), - this.database.unitSetMap().size(), - this.database.dimensionMap().size()); + System.out.println(this.loadStatMsg()); } /** @@ -706,6 +724,17 @@ public final class Presenter { return this.showDuplicates; } + /** + * @return text in About file + * @since 2022-02-19 + */ + public final String getAboutText() { + return Presenter.getLinesFromResource("/about.txt").stream() + .map(Presenter::withoutComments).collect(Collectors.joining("\n")) + .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) + .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); + } + /** * Gets a name for this dimension using the database * @@ -891,6 +920,23 @@ public final class Presenter { } } + /** + * @return a message showing how much stuff has been loaded + * @since 2024-08-22 + */ + private String loadStatMsg() { + return String.format( + "Successfully loaded %d unique units with %d names (%d base units), %d unique prefixes with %d names, %d unit sets, and %d named dimensions.", + this.database.unitMapPrefixless(false).size(), + this.database.unitMapPrefixless(true).size(), + this.database.unitMapPrefixless(false).values().stream() + .filter(IS_FULL_BASE).count(), + this.database.prefixMap(false).size(), + this.database.prefixMap(true).size(), + this.database.unitSetMap().size(), + this.database.dimensionMap().size()); + } + /** * @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 @@ -1072,7 +1118,7 @@ public final class Presenter { * @param u unit to show * @since 2022-04-16 */ - private final void showUnit(Unit u) { + private void showUnit(Unit u) { final var nameSymbol = u.getNameSymbol(); final boolean isBase = u instanceof BaseUnit || u instanceof LinearUnit && ((LinearUnit) u).isBase(); diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index a71de44..6542541 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -357,7 +357,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { infoTextArea.setEditable(false); infoTextArea.setOpaque(false); infoPanel.add(infoTextArea); - infoTextArea.setText(Presenter.getAboutText()); + infoTextArea.setText(this.presenter.getAboutText()); // ============ SETTINGS PANEL ============ this.masterPane.addTab("\u2699", diff --git a/src/main/resources/about.txt b/src/main/resources/about.txt index 782b422..4c33f8c 100644 --- a/src/main/resources/about.txt +++ b/src/main/resources/about.txt @@ -9,6 +9,10 @@ like "10 m/s + (25^2 - 5^2) mi/hr". This software was written by Adrien Hopkins . +Unit/Prefix/Dimension Statistics: + +[LOADSTATS] + Copyright Notice: Unit Converter Copyright (C) 2018-2024 Adrien Hopkins -- cgit v1.2.3 From a9485b187844cad900bc43c0651406c51a7295c1 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 28 Aug 2024 19:23:34 -0500 Subject: Bump version number to 1.0.0a1 --- README.org | 2 +- src/main/java/sevenUnits/ProgramInfo.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.org b/README.org index 4e64591..cf8de11 100644 --- a/README.org +++ b/README.org @@ -1,4 +1,4 @@ -* 7Units Version 0.5.0 +* 7Units Version 1.0.0-alpha.1 (this project uses Semantic Versioning) ** What is it? This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in. diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index 9c23c49..573c5c7 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -25,15 +25,15 @@ import sevenUnits.utils.SemanticVersionNumber; * @since 2021-06-28 */ public final class ProgramInfo { - - /** The version number (0.5.0) */ + + /** The version number (1.0.0-alpha.1) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber - .stableVersion(0, 5, 0); - + .preRelease(1, 0, 0, "alpha", 1); + private ProgramInfo() { // this class is only for static variables, you shouldn't be able to // construct an instance throw new AssertionError(); } - + } -- cgit v1.2.3 From 9a25a05d1b376dc20a14696afa280ff4940b1f8a Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 21 Feb 2025 23:51:13 -0500 Subject: Add internationalization API to GUI This commit intentionally fails one test, since that is for functionality I intend to add later. --- .classpath | 2 +- src/main/java/sevenUnitsGUI/Presenter.java | 58 +++++++++++++++++++++++++++++ src/main/java/sevenUnitsGUI/TabbedView.java | 5 +++ src/main/java/sevenUnitsGUI/View.java | 7 ++++ src/main/java/sevenUnitsGUI/ViewBot.java | 5 +++ src/test/java/sevenUnitsGUI/I18nTest.java | 21 +++++++++++ 6 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/test/java/sevenUnitsGUI/I18nTest.java diff --git a/.classpath b/.classpath index d4e759d..02a1f84 100644 --- a/.classpath +++ b/.classpath @@ -25,6 +25,6 @@ - + diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index c46ee53..21f2951 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -77,6 +78,15 @@ public final class Presenter { /** A Predicate that returns true iff the argument is a full base unit */ private static final Predicate IS_FULL_BASE = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase(); + /** + * The default locale, used in two situations: + *

    + *
  • If no text is available in your locale, + * uses text from this locale. + *
  • Users are initialized with this locale. + *
+ */ + static final String DEFAULT_LOCALE = "en"; /** * Adds default units and dimensions to a database. @@ -317,6 +327,12 @@ public final class Presenter { */ private final Set metricExceptions; + /** maps locale names (e.g. 'en') to key-text maps */ + final Map> locales; + + /** name of locale in locales to use */ + String userLocale; + /** * 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 @@ -375,6 +391,9 @@ public final class Presenter { e); } + // TODO load locales + this.locales = new HashMap<>(); + // set default settings temporarily if (Files.exists(CONFIG_FILE)) { this.loadSettings(CONFIG_FILE); @@ -735,6 +754,14 @@ public final class Presenter { .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); } + /** + * @return set of all locales available to select + * @since 2025-02-21 + */ + public final Set getAvailableLocales() { + return this.locales.keySet(); + } + /** * Gets a name for this dimension using the database * @@ -750,6 +777,20 @@ public final class Presenter { .orElse(dimension.toString(Nameable::getName)); } + /** + * Gets the correct text for a provided ID. + * If this text is available in the user's locale, that text is provided. + * Otherwise, text is taken from the system default locale {@link #DEFAULT_LOCALE}. + * + * @param textID ID of text to get (used in locale files) + * @return text to be displayed + */ + public String getLocalizedText(String textID) { + final Map userLocale = this.locales.get(this.userLocale); + final Map defaultLocale = this.locales.get(DEFAULT_LOCALE); + return userLocale.getOrDefault(textID, defaultLocale.get(textID)); + } + /** * @return the rule that is used by this presenter to convert numbers into * strings @@ -785,6 +826,14 @@ public final class Presenter { return this.searchRule; } + /** + * @return user's selected locale + * @since 2025-02-21 + */ + public String getUserLocale() { + return userLocale; + } + /** * @return a search rule that shows all single prefixes * @since 2022-07-08 @@ -1100,6 +1149,15 @@ public final class Presenter { this.updateView(); } + /** + * Sets the user's locale, updating the view. + * @param userLocale locale to use + */ + public void setUserLocale(String userLocale) { + this.userLocale = userLocale; + this.view.updateText(); + } + private List> settingsFromFile(Path settingsFile) { try (Stream lines = Files.lines(settingsFile)) { return lines.map(Presenter::withoutComments) diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 6542541..493fc10 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -827,4 +827,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.presenter.setNumberDisplayRule(roundingRule); this.presenter.saveSettings(); } + + @Override + public void updateText() { + // TODO Auto-generated method stub + } } diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index 7dd0c44..4140992 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -112,4 +112,11 @@ public interface View { */ void showUnit(NameSymbol name, String definition, String dimensionName, UnitType type); + + /** + * Updates the view's text to reflect the presenter's locale. + * + * This method must not call {@link Presenter#setUserLocale(String)}. + */ + void updateText(); } diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index e6593fb..8fff46d 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -505,4 +505,9 @@ public final class ViewBot public List unitViewList() { return Collections.unmodifiableList(this.unitViewingRecords); } + + @Override + public void updateText() { + // do nothing, since ViewBot is not localized + } } diff --git a/src/test/java/sevenUnitsGUI/I18nTest.java b/src/test/java/sevenUnitsGUI/I18nTest.java new file mode 100644 index 0000000..73bd727 --- /dev/null +++ b/src/test/java/sevenUnitsGUI/I18nTest.java @@ -0,0 +1,21 @@ +package sevenUnitsGUI; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +class I18nTest { + + /** + * Tests that the default locale exists. + * + * Currently this test fails. + */ + @Test + void testDefaultLocale() { + Presenter p = new Presenter(new ViewBot()); + assertNotNull(p.locales.get(Presenter.DEFAULT_LOCALE), + "Default locale does not exist."); + } + +} -- cgit v1.2.3 From d41c05feaaf543c473a9db7aa5a3e564cee0e4ed Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 22 Feb 2025 17:29:28 -0500 Subject: Load locales from text files --- src/main/java/sevenUnitsGUI/Presenter.java | 705 +++++++++++++++------------- src/main/java/sevenUnitsGUI/TabbedView.java | 3 +- src/main/resources/locales/en.txt | 1 + src/main/resources/locales/fr.txt | 1 + 4 files changed, 379 insertions(+), 331 deletions(-) create mode 100644 src/main/resources/locales/en.txt create mode 100644 src/main/resources/locales/fr.txt diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 21f2951..1cbac47 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -16,7 +16,6 @@ */ package sevenUnitsGUI; -import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -58,7 +57,7 @@ import sevenUnitsGUI.StandardDisplayRules.UncertaintyBased; /** * An object that handles interactions between the view and the backend code - * + * * @author Adrien Hopkins * @since 2021-12-15 */ @@ -78,19 +77,23 @@ public final class Presenter { /** A Predicate that returns true iff the argument is a full base unit */ private static final Predicate IS_FULL_BASE = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase(); - /** + /** * The default locale, used in two situations: *
    - *
  • If no text is available in your locale, - * uses text from this locale. - *
  • Users are initialized with this locale. + *
  • If no text is available in your locale, uses text from this locale. + *
  • Users are initialized with this locale. *
*/ - static final String DEFAULT_LOCALE = "en"; - + static final String DEFAULT_LOCALE = "eo"; + + private static final List LOCAL_LOCALES = List.of("en", "fr"); + private static final Path USER_LOCALES_DIR = userConfigDir() + .resolve("SevenUnits").resolve("locales"); + + /** * Adds default units and dimensions to a database. - * + * * @param database database to add to * @since 2019-04-14 * @since v0.2.0 @@ -109,20 +112,20 @@ 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); } - + private static String displayRuleToString( Function numberDisplayRule) { if (numberDisplayRule instanceof FixedDecimals) return String.format("FIXED_DECIMALS %d", ((FixedDecimals) numberDisplayRule).decimalPlaces()); - else if (numberDisplayRule instanceof FixedPrecision) + if (numberDisplayRule instanceof FixedPrecision) return String.format("FIXED_PRECISION %d", ((FixedPrecision) numberDisplayRule).significantFigures()); else if (numberDisplayRule instanceof UncertaintyBased) @@ -130,21 +133,21 @@ public final class Presenter { else return numberDisplayRule.toString(); } - + /** * Determines where to wrap {@code toWrap} with a max line length of * {@code maxLineLength}. If no good spot is found, returns -1. * * @since 2024-08-22 */ - private static final int findLineSplit(String toWrap, int maxLineLength) { - for (int i = maxLineLength - 1; i >= 0; i--) { + private static int findLineSplit(String toWrap, int maxLineLength) { + for (var i = maxLineLength - 1; i >= 0; i--) { if (Character.isWhitespace(toWrap.charAt(i))) return i; } return -1; } - + /** * Gets the text of a resource file as a set of strings (each one is one line * of the text). @@ -153,11 +156,11 @@ public final class Presenter { * @return contents of file * @since 2021-03-27 */ - private static final List getLinesFromResource(String filename) { + private static List getLinesFromResource(String filename) { final List lines = new ArrayList<>(); - - try (InputStream stream = inputStream(filename); - Scanner scanner = new Scanner(stream)) { + + try (var stream = inputStream(filename); + var scanner = new Scanner(stream)) { while (scanner.hasNextLine()) { lines.add(scanner.nextLine()); } @@ -165,10 +168,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. * @@ -176,98 +179,97 @@ public final class Presenter { * @return obtained Path * @since 2021-03-27 */ - private static final InputStream inputStream(String filepath) { + private static InputStream inputStream(String filepath) { return Presenter.class.getResourceAsStream(filepath); } - + /** * Convert a linear unit value to a string, where the number is rounded to * the nearest integer. * * @since 2024-08-16 */ - private static final String linearUnitValueIntToString(LinearUnitValue uv) { + private static String linearUnitValueIntToString(LinearUnitValue uv) { return Long.toString(Math.round(uv.getValueExact())) + " " + uv.getUnit(); } - + private static Map.Entry parseSettingLine(String line) { - final int equalsIndex = line.indexOf('='); + final var 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); - + + final var param = line.substring(0, equalsIndex); + final var value = line.substring(equalsIndex + 1); + return Map.entry(param, value); } - + /** Gets a Path from a pathname in the config file. */ private static Path pathFromConfig(String pathname) { return CONFIG_FILE.getParent().resolve(pathname); } - + // ====== SETTINGS ====== - + private static String searchRuleToString( Function, Map> searchRule) { if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) return "NO_PREFIXES"; - else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) + 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(); } - + /** * @return true iff a and b have any elements in common * @since 2022-04-19 */ - private static final boolean sharesAnyElements(Set a, Set b) { + private static boolean sharesAnyElements(Set a, Set b) { for (final Object e : a) { if (b.contains(e)) return true; } return false; } - - private static final Path userConfigDir() { + + private static Path userConfigDir() { if (System.getProperty("os.name").startsWith("Windows")) { - final String envFolder = System.getenv("LOCALAPPDATA"); + final var 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); } + final var envFolder = System.getenv("XDG_CONFIG_HOME"); + if (envFolder == null || "".equals(envFolder)) + return Path.of(System.getenv("HOME"), ".config"); + else + return Path.of(envFolder); } - + /** * @return {@code line} with any comments removed. * @since 2021-03-13 */ - private static final String withoutComments(String line) { - final int index = line.indexOf('#'); + private static String withoutComments(String line) { + final var index = line.indexOf('#'); return index == -1 ? line : line.substring(0, index); } - + /** * Wraps a string, ensuring no line is longer than {@code maxLineLength}. - * + * * @since 2024-08-22 */ - private static final String wrapString(String toWrap, int maxLineLength) { - final StringBuilder wrapped = new StringBuilder(toWrap.length()); - String remaining = toWrap; + private static String wrapString(String toWrap, int maxLineLength) { + final var wrapped = new StringBuilder(toWrap.length()); + var remaining = toWrap; while (remaining.length() > maxLineLength) { - final int spot = findLineSplit(toWrap, maxLineLength); + final var spot = findLineSplit(toWrap, maxLineLength); if (spot == -1) { wrapped.append(remaining.substring(0, maxLineLength)); wrapped.append("-\n"); @@ -281,23 +283,23 @@ public final class Presenter { wrapped.append(remaining); return wrapped.toString(); } - + /** * 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. Not implemented yet. */ private Function 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 @@ -305,50 +307,50 @@ public final class Presenter { */ private Function 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> 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> 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 metricExceptions; - + /** maps locale names (e.g. 'en') to key-text maps */ final Map> locales; - + /** name of locale in locales to use */ String userLocale; - + /** * 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 - * + * * @param view the view that this presenter communicates with * @since 2021-12-15 */ @@ -356,30 +358,30 @@ 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)) { + try (final var units = inputStream(DEFAULT_UNITS_FILEPATH)) { this.handleLoadErrors(this.database.loadUnitsFromStream(units)); } catch (final IOException e) { throw new AssertionError("Loading of unitsfile.txt failed.", e); } - + // load dimensions - try (final InputStream dimensions = inputStream( + try (final var dimensions = inputStream( DEFAULT_DIMENSIONS_FILEPATH)) { this.handleLoadErrors( this.database.loadDimensionsFromStream(dimensions)); } catch (final IOException e) { throw new AssertionError("Loading of dimensionfile.txt failed.", e); } - + // load metric exceptions try { this.metricExceptions = new HashSet<>(); - try (InputStream exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); - Scanner scanner = new Scanner(exceptions)) { + try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); + var scanner = new Scanner(exceptions)) { while (scanner.hasNextLine()) { - final String line = Presenter + final var line = Presenter .withoutComments(scanner.nextLine()); if (!line.isBlank()) { this.metricExceptions.add(line); @@ -390,19 +392,19 @@ public final class Presenter { throw new AssertionError("Loading of metric_exceptions.txt failed.", e); } - - // TODO load locales - this.locales = new HashMap<>(); - + + this.locales = this.loadLocales(); + this.userLocale = DEFAULT_LOCALE; + // set default settings temporarily if (Files.exists(CONFIG_FILE)) { this.loadSettings(CONFIG_FILE); } - + // print out unit counts System.out.println(this.loadStatMsg()); } - + /** * Applies a search rule to an entry in a name-unit map. * @@ -410,23 +412,23 @@ public final class Presenter { * @return stream of entries, ready for flat-mapping * @since 2022-07-06 */ - private final Stream> applySearchRule( + private Stream> applySearchRule( Map.Entry e) { - final Unit u = e.getValue(); + final var u = e.getValue(); if (u instanceof LinearUnit) { - final String name = e.getKey(); + final var name = e.getKey(); final Map.Entry linearEntry = Map.entry(name, (LinearUnit) u); return this.searchRule.apply(linearEntry).entrySet().stream().map( entry -> Map.entry(entry.getKey(), (Unit) entry.getValue())); - } else - return Stream.of(e); + } + 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. - * + * * @throws UnsupportedOperationException if the view does not support * expression-based conversion (does * not implement @@ -434,49 +436,46 @@ public final class Presenter { * @since 2021-12-15 */ 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", - "Please enter a unit expression in the From: box."); - return; - } - if (toExpression.isEmpty()) { - this.view.showErrorMessage("Parse Error", - "Please enter a unit expression in the To: box."); - return; - } - - final Optional uc; - if (this.database.containsUnitSetName(toExpression)) { - uc = this.convertExpressionToNamedMultiUnit(fromExpression, - toExpression); - } else if (toExpression.contains(";")) { - final String[] toExpressions = toExpression.split(";"); - uc = this.convertExpressionToMultiUnit(fromExpression, - toExpressions); - } else { - uc = this.convertExpressionToExpression(fromExpression, - toExpression); - } - - uc.ifPresent(xcview::showExpressionConversionOutput); - } else + if (!(this.view instanceof ExpressionConversionView)) throw new UnsupportedOperationException( "This function can only be called when the view is an ExpressionConversionView"); + final var xcview = (ExpressionConversionView) this.view; + + final var fromExpression = xcview.getFromExpression(); + final var toExpression = xcview.getToExpression(); + + // expressions must not be empty + if (fromExpression.isEmpty()) { + this.view.showErrorMessage("Parse Error", + "Please enter a unit expression in the From: box."); + return; + } + if (toExpression.isEmpty()) { + this.view.showErrorMessage("Parse Error", + "Please enter a unit expression in the To: box."); + return; + } + + final Optional uc; + if (this.database.containsUnitSetName(toExpression)) { + uc = this.convertExpressionToNamedMultiUnit(fromExpression, + toExpression); + } else if (toExpression.contains(";")) { + final var toExpressions = toExpression.split(";"); + uc = this.convertExpressionToMultiUnit(fromExpression, toExpressions); + } else { + uc = this.convertExpressionToExpression(fromExpression, toExpression); + } + + uc.ifPresent(xcview::showExpressionConversionOutput); } - + /** * Converts a unit expression to another expression. - * + * * If an error happened, it is shown to the view and Optional.empty() is * returned. - * + * * @since 2024-08-15 */ private Optional convertExpressionToExpression( @@ -498,34 +497,33 @@ public final class Presenter { "Could not recognize text in To entry: " + e.getMessage()); return Optional.empty(); } - + // 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) { - final var toLinear = (LinearUnit) to; - uncertainValue = from.convertTo(toLinear).getValue(); - } else { - final double value = from.asUnitValue().convertTo(to).getValue(); - uncertainValue = UncertainDouble.of(value, 0); - } - - final UnitConversionRecord uc = UnitConversionRecord.valueOf( - fromExpression, toExpression, "", - this.numberDisplayRule.apply(uncertainValue)); - return Optional.of(uc); - } else { + if (!from.getUnit().canConvertTo(to)) { this.view.showErrorMessage("Conversion Error", "Cannot convert between \"" + fromExpression + "\" and \"" + toExpression + "\"."); return Optional.empty(); } - + final UncertainDouble uncertainValue; + + // uncertainty is meaningless for non-linear units, so we will have + // to erase uncertainty information for them + if (to instanceof LinearUnit) { + final var toLinear = (LinearUnit) to; + uncertainValue = from.convertTo(toLinear).getValue(); + } else { + final var value = from.asUnitValue().convertTo(to).getValue(); + uncertainValue = UncertainDouble.of(value, 0); + } + + final var uc = UnitConversionRecord.valueOf( + fromExpression, toExpression, "", + this.numberDisplayRule.apply(uncertainValue)); + return Optional.of(uc); + } - + /** * Convert an expression to a MultiUnit. If an error happened, it is shown to * the view and Optional.empty() is returned. @@ -542,27 +540,26 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - + final List toUnits = new ArrayList<>(toExpressions.length); - for (int i = 0; i < toExpressions.length; i++) { + for (final String toExpression : toExpressions) { try { - final Unit toI = this.database - .getUnitFromExpression(toExpressions[i].trim()); - if (toI instanceof LinearUnit) { - toUnits.add((LinearUnit) toI); - } else { + final var toI = this.database + .getUnitFromExpression(toExpression.trim()); + if (!(toI instanceof LinearUnit)) { this.view.showErrorMessage("Unit Type Error", "Units separated by ';' must be linear; " + toI + " is not."); return Optional.empty(); } + toUnits.add((LinearUnit) toI); } catch (final IllegalArgumentException | NoSuchElementException e) { this.view.showErrorMessage("Parse Error", "Could not recognize text in To entry: " + e.getMessage()); return Optional.empty(); } } - + final List toValues; try { toValues = from.convertToMultiple(toUnits); @@ -571,12 +568,12 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - - final String toExpression = this.linearUnitValueSumToString(toValues); + + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Convert an expression to a MultiUnit with a name from the database. If an * error happened, it is shown to the view and Optional.empty() is returned. @@ -593,8 +590,8 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - - final List toUnits = this.database.getUnitSet(toName); + + final var toUnits = this.database.getUnitSet(toName); final List toValues; try { toValues = from.convertToMultiple(toUnits); @@ -603,16 +600,16 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - - final String toExpression = this.linearUnitValueSumToString(toValues); + + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Converts from the view's input unit to its output unit. Displays an error * message if any of the required fields are invalid. - * + * * @throws UnsupportedOperationException if the view does not support * unit-based conversion (does not * implement @@ -620,64 +617,61 @@ public final class Presenter { * @since 2021-12-15 */ public void convertUnits() { - if (this.view instanceof UnitConversionView) { - final UnitConversionView ucview = (UnitConversionView) this.view; - - final Optional fromUnitOptional = ucview.getFromSelection(); - final Optional toUnitOptional = ucview.getToSelection(); - final String inputValueString = ucview.getInputValue(); - - // extract values from optionals - final String fromUnitString, toUnitString; - if (fromUnitOptional.isPresent()) { - fromUnitString = fromUnitOptional.orElseThrow(); - } else { - this.view.showErrorMessage("Unit Selection Error", - "Please specify a From unit"); - return; - } - if (toUnitOptional.isPresent()) { - toUnitString = toUnitOptional.orElseThrow(); - } else { - this.view.showErrorMessage("Unit Selection Error", - "Please specify a To unit"); - return; - } - - // convert strings to data, checking if anything is invalid - final Unit fromUnit; - final UncertainDouble uncertainValue; - - if (this.database.containsUnitName(fromUnitString)) { - fromUnit = this.database.getUnit(fromUnitString); - } else - throw this.viewError("Nonexistent From unit: %s", fromUnitString); - try { - uncertainValue = UncertainDouble - .fromRoundedString(inputValueString); - } catch (final NumberFormatException e) { - this.view.showErrorMessage("Value Error", - "Invalid value " + inputValueString); - return; - } - if (this.database.containsUnitName(toUnitString)) { - final Unit toUnit = this.database.getUnit(toUnitString); - ucview.showUnitConversionOutput( - this.convertUnitToUnit(fromUnitString, toUnitString, - inputValueString, fromUnit, toUnit, uncertainValue)); - } else if (this.database.containsUnitSetName(toUnitString)) { - final List toMulti = this.database - .getUnitSet(toUnitString); - ucview.showUnitConversionOutput( - this.convertUnitToMulti(fromUnitString, inputValueString, - fromUnit, toMulti, uncertainValue)); - } else - throw this.viewError("Nonexistent To unit: %s", toUnitString); - } else + if (!(this.view instanceof UnitConversionView)) throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); + final var ucview = (UnitConversionView) this.view; + + final var fromUnitOptional = ucview.getFromSelection(); + final var toUnitOptional = ucview.getToSelection(); + final var inputValueString = ucview.getInputValue(); + + // extract values from optionals + final String fromUnitString, toUnitString; + if (fromUnitOptional.isPresent()) { + fromUnitString = fromUnitOptional.orElseThrow(); + } else { + this.view.showErrorMessage("Unit Selection Error", + "Please specify a From unit"); + return; + } + if (toUnitOptional.isPresent()) { + toUnitString = toUnitOptional.orElseThrow(); + } else { + this.view.showErrorMessage("Unit Selection Error", + "Please specify a To unit"); + return; + } + + // convert strings to data, checking if anything is invalid + final Unit fromUnit; + final UncertainDouble uncertainValue; + + if (this.database.containsUnitName(fromUnitString)) { + fromUnit = this.database.getUnit(fromUnitString); + } else + throw this.viewError("Nonexistent From unit: %s", fromUnitString); + try { + uncertainValue = UncertainDouble.fromRoundedString(inputValueString); + } catch (final NumberFormatException e) { + this.view.showErrorMessage("Value Error", + "Invalid value " + inputValueString); + return; + } + if (this.database.containsUnitName(toUnitString)) { + final var toUnit = this.database.getUnit(toUnitString); + ucview.showUnitConversionOutput( + this.convertUnitToUnit(fromUnitString, toUnitString, + inputValueString, fromUnit, toUnit, uncertainValue)); + } else if (this.database.containsUnitSetName(toUnitString)) { + final var toMulti = this.database + .getUnitSet(toUnitString); + ucview.showUnitConversionOutput(this.convertUnitToMulti(fromUnitString, + inputValueString, fromUnit, toMulti, uncertainValue)); + } else + throw this.viewError("Nonexistent To unit: %s", toUnitString); } - + private UnitConversionRecord convertUnitToMulti(String fromUnitString, String inputValueString, Unit fromUnit, List toMulti, UncertainDouble uncertainValue) { @@ -686,7 +680,7 @@ public final class Presenter { throw this.viewError("Could not convert between %s and %s", fromUnit, toUnit); } - + final LinearUnitValue initValue; if (fromUnit instanceof LinearUnit) { final var fromLinear = (LinearUnit) fromUnit; @@ -695,46 +689,46 @@ public final class Presenter { initValue = UnitValue.of(fromUnit, uncertainValue.value()) .convertToBase(NameSymbol.EMPTY); } - - final List converted = initValue + + final var converted = initValue .convertToMultiple(toMulti); - final String toExpression = this.linearUnitValueSumToString(converted); + final var toExpression = this.linearUnitValueSumToString(converted); return UnitConversionRecord.valueOf(fromUnitString, toExpression, inputValueString, ""); - + } - + private UnitConversionRecord convertUnitToUnit(String fromUnitString, String toUnitString, String inputValueString, Unit fromUnit, Unit toUnit, UncertainDouble uncertainValue) { 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; if (fromUnit instanceof LinearUnit && toUnit instanceof LinearUnit) { - final LinearUnit fromLinear = (LinearUnit) fromUnit; - final LinearUnit toLinear = (LinearUnit) toUnit; - final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear, + final var fromLinear = (LinearUnit) fromUnit; + final var toLinear = (LinearUnit) toUnit; + final var initialValue = LinearUnitValue.of(fromLinear, uncertainValue); - final LinearUnitValue converted = initialValue.convertTo(toLinear); - + final var converted = initialValue.convertTo(toLinear); + outputValueString = this.numberDisplayRule.apply(converted.getValue()); } else { - final UnitValue initialValue = UnitValue.of(fromUnit, + final var initialValue = UnitValue.of(fromUnit, uncertainValue.value()); - final UnitValue converted = initialValue.convertTo(toUnit); - + final var converted = initialValue.convertTo(toUnit); + outputValueString = this.numberDisplayRule .apply(UncertainDouble.of(converted.getValue(), 0)); } - + return UnitConversionRecord.valueOf(fromUnitString, toUnitString, inputValueString, outputValueString); } - + /** * @return true iff duplicate units are shown in unit lists * @since 2022-03-30 @@ -742,26 +736,26 @@ public final class Presenter { public boolean duplicatesShown() { return this.showDuplicates; } - + /** * @return text in About file * @since 2022-02-19 */ - public final String getAboutText() { + public String getAboutText() { return Presenter.getLinesFromResource("/about.txt").stream() .map(Presenter::withoutComments).collect(Collectors.joining("\n")) .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); } - + /** * @return set of all locales available to select * @since 2025-02-21 */ - public final Set getAvailableLocales() { + public Set getAvailableLocales() { return this.locales.keySet(); } - + /** * Gets a name for this dimension using the database * @@ -769,28 +763,28 @@ public final class Presenter { * @return name of dimension * @since 2022-04-16 */ - final String getDimensionName(ObjectProduct dimension) { + String getDimensionName(ObjectProduct dimension) { // find this dimension in the database and get its name // if it isn't there, use the dimension's toString instead return this.database.dimensionMap().values().stream() .filter(d -> d.equals(dimension)).findAny().map(Nameable::getName) .orElse(dimension.toString(Nameable::getName)); } - + /** - * Gets the correct text for a provided ID. - * If this text is available in the user's locale, that text is provided. - * Otherwise, text is taken from the system default locale {@link #DEFAULT_LOCALE}. - * + * Gets the correct text for a provided ID. If this text is available in the + * user's locale, that text is provided. Otherwise, text is taken from the + * system default locale {@link #DEFAULT_LOCALE}. + * * @param textID ID of text to get (used in locale files) * @return text to be displayed */ public String getLocalizedText(String textID) { - final Map userLocale = this.locales.get(this.userLocale); - final Map defaultLocale = this.locales.get(DEFAULT_LOCALE); + final var userLocale = this.locales.get(this.userLocale); + final var defaultLocale = this.locales.get(DEFAULT_LOCALE); return userLocale.getOrDefault(textID, defaultLocale.get(textID)); } - + /** * @return the rule that is used by this presenter to convert numbers into * strings @@ -799,7 +793,7 @@ public final class Presenter { public Function getNumberDisplayRule() { return this.numberDisplayRule; } - + /** * @return the rule that is used by this presenter to convert strings into * numbers @@ -809,7 +803,7 @@ public final class Presenter { private Function getNumberParsingRule() { return this.numberParsingRule; } - + /** * @return the rule that determines whether a set of prefixes is valid * @since 2022-04-19 @@ -817,7 +811,7 @@ public final class Presenter { public Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * @return the rule that determines which units are prefixed * @since 2022-07-08 @@ -825,15 +819,7 @@ public final class Presenter { public Function, Map> getSearchRule() { return this.searchRule; } - - /** - * @return user's selected locale - * @since 2025-02-21 - */ - public String getUserLocale() { - return userLocale; - } - + /** * @return a search rule that shows all single prefixes * @since 2022-07-08 @@ -842,7 +828,15 @@ public final class Presenter { return PrefixSearchRule.getCoherentOnlyRule( new HashSet<>(this.database.prefixMap(true).values())); } - + + /** + * @return user's selected locale + * @since 2025-02-21 + */ + public String getUserLocale() { + return userLocale; + } + /** * @return the view associated with this presenter * @since 2022-04-19 @@ -850,7 +844,7 @@ public final class Presenter { public View getView() { return this.view; } - + /** * Accepts a list of errors. If that list is non-empty, prints an error * message and alerts the user. @@ -859,7 +853,7 @@ public final class Presenter { */ private void handleLoadErrors(List errors) { if (!errors.isEmpty()) { - final String errorMessage = String.format( + final var errorMessage = String.format( "%d error(s) happened while loading file:\n%s\n", errors.size(), errors.stream().map(Throwable::getMessage) .collect(Collectors.joining("\n"))); @@ -868,13 +862,13 @@ public final class Presenter { errorMessage); } } - + /** * @return whether or not the provided unit is semi-metric (i.e. an * exception) * @since 2022-04-16 */ - final boolean isSemiMetric(Unit u) { + boolean isSemiMetric(Unit u) { // determine if u is an exception final var primaryName = u.getPrimaryName(); final var symbol = u.getSymbol(); @@ -884,7 +878,7 @@ public final class Presenter { && this.metricExceptions.contains(symbol.orElseThrow()) || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } - + /** * Convert a list of LinearUnitValues that you would get from a unit-set * conversion to a string. All but the last have their numbers rendered as @@ -893,18 +887,17 @@ public final class Presenter { * * @since 2024-08-16 */ - private final String linearUnitValueSumToString( - List values) { - final String integerPart = values.subList(0, values.size() - 1).stream() + private String linearUnitValueSumToString(List values) { + final var integerPart = values.subList(0, values.size() - 1).stream() .map(Presenter::linearUnitValueIntToString) .collect(Collectors.joining(" + ")); - final LinearUnitValue last = values.get(values.size() - 1); + final var last = values.get(values.size() - 1); return integerPart + " + " + this.numberDisplayRule.apply(last.getValue()) + " " + last.getUnit(); } - + private void loadExceptionFile(Path exceptionFile) { - try (Stream lines = Files.lines(exceptionFile)) { + try (var lines = Files.lines(exceptionFile)) { lines.map(Presenter::withoutComments) .forEach(this.metricExceptions::add); } catch (final IOException e) { @@ -913,7 +906,54 @@ public final class Presenter { + exceptionFile + "\": " + e.getLocalizedMessage()); } } + + /** + * Loads all available locales, including custom ones, into a map. + * @return map containing locales + */ + private Map> loadLocales() { + final Map> locales = new HashMap<>(); + for (final String localeName : LOCAL_LOCALES) { + final Map locale = new HashMap<>(); + String filename = "/locales/" + localeName + ".txt"; + getLinesFromResource(filename).forEach(line -> addLocaleLine(locale, line)); + locales.put(localeName, locale); + } + + if (Files.exists(USER_LOCALES_DIR)) { + try { + Files.list(USER_LOCALES_DIR).forEach( + localeFile -> { + try { + addLocaleFile(locales, localeFile); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + return locales; + } + private void addLocaleFile(Map> locales, Path file) throws IOException { + final Map locale = new HashMap<>(); + String fileName = file.getName(file.getNameCount()-1).toString(); + String localeName = fileName.substring(0, fileName.length()-4); + Files.lines(file).forEach(line -> addLocaleLine(locale, line)); + locales.put(localeName, locale); + } + + private void addLocaleLine(Map locale, String line) { + String[] parts = line.split("=", 2); + if (parts.length < 2) { + return; + } + + locale.put(parts[0], parts[1]); + } + /** * Loads settings from the user's settings file and applies them to the * presenter. @@ -924,8 +964,8 @@ public final class Presenter { void loadSettings(Path settingsFile) { for (final Map.Entry setting : this .settingsFromFile(settingsFile)) { - final String value = setting.getValue(); - + final var value = setting.getValue(); + switch (setting.getKey()) { // set manually to avoid the unnecessary saving of the non-manual // methods @@ -949,26 +989,29 @@ public final class Presenter { this.database.setPrefixRepetitionRule(this.prefixRepetitionRule); break; case "one_way": - this.oneWayConversionEnabled = Boolean.valueOf(value); + this.oneWayConversionEnabled = Boolean.parseBoolean(value); break; case "include_duplicates": - this.showDuplicates = Boolean.valueOf(value); + this.showDuplicates = Boolean.parseBoolean(value); break; case "search_prefix_rule": this.setSearchRuleFromString(value); break; + case "locale": + this.setUserLocale(value); + break; default: System.err.printf("Warning: unrecognized setting \"%s\".%n", setting.getKey()); break; } } - + if (this.view.getPresenter() != null) { this.updateView(); } } - + /** * @return a message showing how much stuff has been loaded * @since 2024-08-22 @@ -985,37 +1028,37 @@ public final class Presenter { this.database.unitSetMap().size(), this.database.dimensionMap().size()); } - + /** * @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 * unit list and imperial/USC units removed from the To unit list) - * + * * @since 2022-03-30 */ 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 * they depend on are not created yet. - * + * * @since 2022-02-26 */ public void postViewInitialize() { // unit conversion specific stuff if (this.view instanceof UnitConversionView) { - final UnitConversionView ucview = (UnitConversionView) this.view; + final var ucview = (UnitConversionView) this.view; ucview.setDimensionNames(this.database.dimensionMap().keySet()); } - + this.updateView(); } - + void prefixSelected() { - final Optional selectedPrefixName = this.view + final var selectedPrefixName = this.view .getViewedPrefixName(); final Optional selectedPrefix = selectedPrefixName .map(name -> this.database.containsPrefixName(name) @@ -1025,16 +1068,16 @@ public final class Presenter { .ifPresent(prefix -> this.view.showPrefix(prefix.getNameSymbol(), String.valueOf(prefix.getMultiplier()))); } - + /** * 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 boolean saveSettings() { - final Path configDir = CONFIG_FILE.getParent(); + final var configDir = CONFIG_FILE.getParent(); if (!Files.exists(configDir)) { try { Files.createDirectories(configDir); @@ -1042,19 +1085,19 @@ public final class Presenter { return false; } } - + return this.writeSettings(CONFIG_FILE); } - + private void setDisplayRuleFromString(String ruleString) { - final String[] tokens = ruleString.split(" "); + final var tokens = ruleString.split(" "); switch (tokens[0]) { case "FIXED_DECIMALS": - final int decimals = Integer.parseInt(tokens[1]); + final var decimals = Integer.parseInt(tokens[1]); this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals); break; case "FIXED_PRECISION": - final int sigDigs = Integer.parseInt(tokens[1]); + final var sigDigs = Integer.parseInt(tokens[1]); this.numberDisplayRule = StandardDisplayRules.fixedPrecision(sigDigs); break; case "UNCERTAINTY_BASED": @@ -1066,7 +1109,7 @@ public final class Presenter { break; } } - + /** * @param numberDisplayRule the new rule that will be used by this presenter * to convert numbers into strings @@ -1076,7 +1119,7 @@ public final class Presenter { Function numberDisplayRule) { this.numberDisplayRule = numberDisplayRule; } - + /** * @param numberParsingRule the new rule that will be used by this presenter * to convert strings into numbers @@ -1087,7 +1130,7 @@ public final class Presenter { Function numberParsingRule) { this.numberParsingRule = numberParsingRule; } - + /** * @param oneWayConversionEnabled whether not one-way conversion should be * enabled @@ -1098,7 +1141,7 @@ public final class Presenter { this.oneWayConversionEnabled = oneWayConversionEnabled; this.updateView(); } - + /** * @param prefixRepetitionRule the rule that determines whether a set of * prefixes is valid @@ -1109,7 +1152,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 @@ -1121,7 +1164,7 @@ public final class Presenter { Function, Map> searchRule) { this.searchRule = searchRule; } - + private void setSearchRuleFromString(String ruleString) { switch (ruleString) { case "NO_PREFIXES": @@ -1139,7 +1182,7 @@ public final class Presenter { ruleString); } } - + /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 @@ -1148,18 +1191,9 @@ public final class Presenter { this.showDuplicates = showDuplicateUnits; this.updateView(); } - - /** - * Sets the user's locale, updating the view. - * @param userLocale locale to use - */ - public void setUserLocale(String userLocale) { - this.userLocale = userLocale; - this.view.updateText(); - } - + private List> settingsFromFile(Path settingsFile) { - try (Stream lines = Files.lines(settingsFile)) { + try (var lines = Files.lines(settingsFile)) { return lines.map(Presenter::withoutComments) .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine) .toList(); @@ -1169,7 +1203,17 @@ public final class Presenter { return null; } } - + + /** + * Sets the user's locale, updating the view. + * + * @param userLocale locale to use + */ + public void setUserLocale(String userLocale) { + this.userLocale = userLocale; + this.view.updateText(); + } + /** * Shows a unit in the unit viewer * @@ -1178,53 +1222,53 @@ public final class Presenter { */ private void showUnit(Unit u) { final var nameSymbol = u.getNameSymbol(); - final boolean isBase = u instanceof BaseUnit + final var isBase = u instanceof BaseUnit || u instanceof LinearUnit && ((LinearUnit) u).isBase(); final var definition = isBase ? "(Base unit)" : u.toDefinitionString(); final var dimensionString = this.getDimensionName(u.getDimension()); 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. - * + * * @since 2022-04-10 */ void unitNameSelected() { // get selected unit, if it's there and valid - final Optional selectedUnitName = this.view.getViewedUnitName(); + final var selectedUnitName = this.view.getViewedUnitName(); final Optional selectedUnit = selectedUnitName .map(unitName -> this.database.containsUnitName(unitName) ? this.database.getUnit(unitName) : null); selectedUnit.ifPresent(this::showUnit); } - + /** * Updates the view's From and To units, if it has some - * + * * @since 2021-12-15 */ public void updateView() { if (this.view instanceof UnitConversionView) { - final UnitConversionView ucview = (UnitConversionView) this.view; + final var 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(); var unitSets = this.database.unitSetMap().entrySet().stream(); - + // filter by dimension, if one is selected if (selectedDimensionName.isPresent()) { final var viewDimension = this.database @@ -1236,7 +1280,7 @@ public final class Presenter { unitSets = unitSets.filter(us -> viewDimension .equals(us.getValue().get(0).getDimension())); } - + // filter by unit type, if desired if (this.oneWayConversionEnabled) { fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(), @@ -1247,7 +1291,7 @@ public final class Presenter { unitSets = unitSets .filter(us -> this.metricExceptions.contains(us.getKey())); } - + // set unit names ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule) .map(Map.Entry::getKey).collect(Collectors.toSet())); @@ -1259,7 +1303,7 @@ public final class Presenter { ucview.setToUnitNames(toNames); } } - + /** * @param message message to add * @param args string formatting arguments for message @@ -1271,15 +1315,15 @@ public final class Presenter { return new AssertionError("View Programming Error (from " + this.view + "): " + String.format(message, args)); } - + /** * Saves the presenter's settings to the user settings file. - * + * * @param settingsFile file settings should be saved to * @since 2021-12-15 */ boolean writeSettings(Path settingsFile) { - try (BufferedWriter writer = Files.newBufferedWriter(settingsFile)) { + try (var writer = Files.newBufferedWriter(settingsFile)) { writer.write(String.format("number_display_rule=%s\n", displayRuleToString(this.numberDisplayRule))); writer.write( @@ -1290,6 +1334,7 @@ public final class Presenter { String.format("include_duplicates=%s\n", this.showDuplicates)); writer.write(String.format("search_prefix_rule=%s\n", searchRuleToString(this.searchRule))); + writer.write(String.format("locale=%s\n", this.userLocale)); return true; } catch (final IOException e) { e.printStackTrace(); diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 493fc10..cb0a4d2 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -218,7 +218,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // initialize important components this.presenter = new Presenter(this); - this.frame = new JFrame("7Units " + ProgramInfo.VERSION); + this.frame = new JFrame(this.presenter.getLocalizedText("tv.title") + .replace("[v]", ProgramInfo.VERSION.toString())); this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // master components (those that contain everything else within them) diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt new file mode 100644 index 0000000..9d453c9 --- /dev/null +++ b/src/main/resources/locales/en.txt @@ -0,0 +1 @@ +tv.title=7Units [v] \ No newline at end of file diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt new file mode 100644 index 0000000..1d08fda --- /dev/null +++ b/src/main/resources/locales/fr.txt @@ -0,0 +1 @@ +tv.title=7Unités [v] \ No newline at end of file -- cgit v1.2.3 From f1ca636346f4fe4da66035ae30469f1503261a83 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 22 Feb 2025 17:48:23 -0500 Subject: Add ability for user to change locale --- src/main/java/sevenUnitsGUI/Presenter.java | 5 +++-- src/main/java/sevenUnitsGUI/TabbedView.java | 29 +++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 1cbac47..6467f03 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -84,7 +84,7 @@ public final class Presenter { *
  • Users are initialized with this locale. * */ - static final String DEFAULT_LOCALE = "eo"; + static final String DEFAULT_LOCALE = "en"; private static final List LOCAL_LOCALES = List.of("en", "fr"); private static final Path USER_LOCALES_DIR = userConfigDir() @@ -998,7 +998,7 @@ public final class Presenter { this.setSearchRuleFromString(value); break; case "locale": - this.setUserLocale(value); + this.userLocale = value; break; default: System.err.printf("Warning: unrecognized setting \"%s\".%n", @@ -1055,6 +1055,7 @@ public final class Presenter { } this.updateView(); + this.view.updateText(); } void prefixSelected() { diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index cb0a4d2..1da119e 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -196,6 +196,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { private final JTextArea prefixTextBox; // SETTINGS STUFF + private final JComboBox localeSelector; private StandardRoundingType roundingType; private int precision; @@ -218,8 +219,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // initialize important components this.presenter = new Presenter(this); - this.frame = new JFrame(this.presenter.getLocalizedText("tv.title") - .replace("[v]", ProgramInfo.VERSION.toString())); + this.frame = new JFrame("7Units (Unlocalized)"); this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // master components (those that contain everything else within them) @@ -361,12 +361,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { infoTextArea.setText(this.presenter.getAboutText()); // ============ SETTINGS PANEL ============ + this.localeSelector = new JComboBox<>(); this.masterPane.addTab("\u2699", new JScrollPane(this.createSettingsPanel())); this.masterPane.setMnemonicAt(5, KeyEvent.VK_S); // ============ FINALIZE CREATION OF VIEW ============ this.presenter.postViewInitialize(); + this.updateText(); this.frame.pack(); this.frame.setVisible(true); } @@ -604,7 +606,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { e.getStateChange() == ItemEvent.SELECTED); this.presenter.saveSettings(); }); - miscPanel.add(oneWay, new GridBagBuilder(0, 0) + miscPanel.add(oneWay, new GridBagBuilder(0, 0, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); final JCheckBox showAllVariations = new JCheckBox( @@ -615,12 +617,26 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { .setShowDuplicates(e.getStateChange() == ItemEvent.SELECTED); this.presenter.saveSettings(); }); - miscPanel.add(showAllVariations, new GridBagBuilder(0, 1) + miscPanel.add(showAllVariations, new GridBagBuilder(0, 1, 2, 1) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JLabel localeLabel = new JLabel("Locale:"); + miscPanel.add(localeLabel, new GridBagBuilder(0, 2, 1, 1) .setAnchor(GridBagConstraints.LINE_START).build()); + this.presenter.getAvailableLocales().stream().sorted() + .forEachOrdered(this.localeSelector::addItem); + this.localeSelector.setSelectedItem(this.presenter.getUserLocale()); + this.localeSelector.addItemListener(e -> { + this.presenter.setUserLocale((String) e.getItem()); + this.presenter.saveSettings(); + }); + miscPanel.add(localeSelector, new GridBagBuilder(1, 2, 1, 1) + .setAnchor(GridBagConstraints.LINE_END).build()); + final JButton unitFileButton = new JButton("Manage Unit Data Files"); unitFileButton.setEnabled(false); - miscPanel.add(unitFileButton, new GridBagBuilder(0, 2) + miscPanel.add(unitFileButton, new GridBagBuilder(0, 3, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); } @@ -831,6 +847,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { @Override public void updateText() { - // TODO Auto-generated method stub + this.frame.setTitle(this.presenter.getLocalizedText("tv.title") + .replace("[v]", ProgramInfo.VERSION.toString())); } } -- cgit v1.2.3 From a62fd9e6b4519ffcbd0503c45b159207ce438243 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 17:48:55 -0500 Subject: Localize all user-facing strings --- src/main/java/sevenUnitsGUI/TabbedView.java | 114 ++++++++++++++++++++-------- src/main/resources/locales/en.txt | 30 +++++++- 2 files changed, 110 insertions(+), 34 deletions(-) diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 1da119e..ca9f23c 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -24,13 +24,16 @@ import java.awt.event.ItemEvent; import java.awt.event.KeyEvent; import java.util.AbstractSet; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import javax.swing.BorderFactory; @@ -200,6 +203,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { private StandardRoundingType roundingType; private int precision; + private final Map> localizedTextSetters; + /** * Creates the view and makes it visible to the user * @@ -226,9 +231,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.masterPane = new JTabbedPane(); this.frame.add(this.masterPane); + this.localizedTextSetters = new HashMap<>(); + // ============ UNIT CONVERSION TAB ============ final JPanel convertUnitPanel = new JPanel(); this.masterPane.addTab("Convert Units", convertUnitPanel); + this.localizedTextSetters.put("tv.convert_units.title", + txt -> this.masterPane.setTitleAt(0, txt)); this.masterPane.setMnemonicAt(0, KeyEvent.VK_U); convertUnitPanel.setLayout(new BorderLayout()); @@ -264,7 +273,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { outputPanel.setLayout(new BorderLayout()); outputPanel.setBorder(new EmptyBorder(3, 6, 6, 6)); - final JLabel valuePrompt = new JLabel("Value to convert: "); + final JLabel valuePrompt = new JLabel(); + this.localizedTextSetters.put("tv.convert_units.value_prompt", + valuePrompt::setText); outputPanel.add(valuePrompt, BorderLayout.LINE_START); this.valueInput = new JTextField(); @@ -272,6 +283,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // conversion button this.convertUnitButton = new JButton("Convert"); + this.localizedTextSetters.put("tv.convert_units.convert_btn", + this.convertUnitButton::setText); outputPanel.add(this.convertUnitButton, BorderLayout.LINE_END); this.convertUnitButton .addActionListener(e -> this.presenter.convertUnits()); @@ -287,20 +300,26 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { final JPanel convertExpressionPanel = new JPanel(); this.masterPane.addTab("Convert Unit Expressions", convertExpressionPanel); + this.localizedTextSetters.put("tv.convert_expressions.title", + txt -> this.masterPane.setTitleAt(1, txt)); 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.localizedTextSetters.put("tv.convert_expressions.from", + txt -> this.fromEntry.setBorder(BorderFactory.createTitledBorder(txt))); this.toEntry = new JTextField(); convertExpressionPanel.add(this.toEntry); - this.toEntry.setBorder(BorderFactory.createTitledBorder("To")); + this.localizedTextSetters.put("tv.convert_expressions.to", + txt -> this.toEntry.setBorder(BorderFactory.createTitledBorder(txt))); // button to convert - this.convertExpressionButton = new JButton("Convert"); + this.convertExpressionButton = new JButton(); + this.localizedTextSetters.put("tv.convert_expressions.convert_btn", + this.convertExpressionButton::setText); convertExpressionPanel.add(this.convertExpressionButton); this.convertExpressionButton @@ -310,13 +329,15 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // output of conversion this.expressionOutput = new JTextArea(2, 32); convertExpressionPanel.add(this.expressionOutput); - this.expressionOutput - .setBorder(BorderFactory.createTitledBorder("Output")); + this.localizedTextSetters.put("tv.convert_expressions.output", + txt -> this.expressionOutput.setBorder(BorderFactory.createTitledBorder(txt))); this.expressionOutput.setEditable(false); // =========== UNIT VIEWER =========== final JPanel unitLookupPanel = new JPanel(); this.masterPane.addTab("Unit Viewer", unitLookupPanel); + this.localizedTextSetters.put("tv.unit_viewer.title", + txt -> this.masterPane.setTitleAt(2, txt)); this.masterPane.setMnemonicAt(2, KeyEvent.VK_V); unitLookupPanel.setLayout(new GridLayout()); @@ -334,6 +355,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // ============ PREFIX VIEWER ============= final JPanel prefixLookupPanel = new JPanel(); this.masterPane.addTab("Prefix Viewer", prefixLookupPanel); + this.localizedTextSetters.put("tv.prefix_viewer.title", + txt -> this.masterPane.setTitleAt(3, txt)); this.masterPane.setMnemonicAt(3, KeyEvent.VK_P); prefixLookupPanel.setLayout(new GridLayout(1, 2)); @@ -389,7 +412,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { { final JPanel roundingPanel = new JPanel(); settingsPanel.add(roundingPanel); - roundingPanel.setBorder(new TitledBorder("Rounding Settings")); + this.localizedTextSetters.put("tv.settings.rounding.title", + txt -> roundingPanel.setBorder(new TitledBorder(txt))); roundingPanel.setLayout(new GridBagLayout()); // rounding rule selection @@ -399,13 +423,17 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { "Presenter loaded non-standard rounding rule")); this.precision = this.getPresenterPrecision().orElse(6); - final JLabel roundingRuleLabel = new JLabel("Rounding Rule:"); + final JLabel roundingRuleLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.rounding.rule", + roundingRuleLabel::setText); 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:"); + final JLabel sliderLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.rounding.precision", + sliderLabel::setText); sliderLabel.setVisible( this.roundingType != StandardRoundingType.UNCERTAINTY); roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4) @@ -431,8 +459,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { }); // significant digit rounding - final JRadioButton fixedPrecision = new JRadioButton( - "Fixed Precision"); + final JRadioButton fixedPrecision = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.fixed_sigfig", + fixedPrecision::setText); if (this.roundingType == StandardRoundingType.SIGNIFICANT_DIGITS) { fixedPrecision.setSelected(true); } @@ -447,8 +476,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { .setAnchor(GridBagConstraints.LINE_START).build()); // decimal place rounding - final JRadioButton fixedDecimals = new JRadioButton( - "Fixed Decimal Places"); + final JRadioButton fixedDecimals = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.fixed_places", + fixedDecimals::setText); if (this.roundingType == StandardRoundingType.DECIMAL_PLACES) { fixedDecimals.setSelected(true); } @@ -463,8 +493,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { .setAnchor(GridBagConstraints.LINE_START).build()); // scientific rounding - final JRadioButton relativePrecision = new JRadioButton( - "Uncertainty-Based Rounding"); + final JRadioButton relativePrecision = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.uncertainty", + relativePrecision::setText); if (this.roundingType == StandardRoundingType.UNCERTAINTY) { relativePrecision.setSelected(true); } @@ -483,8 +514,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { { final JPanel prefixRepetitionPanel = new JPanel(); settingsPanel.add(prefixRepetitionPanel); - prefixRepetitionPanel - .setBorder(new TitledBorder("Prefix Repetition Settings")); + this.localizedTextSetters.put("tv.settings.repetition.title", + txt -> prefixRepetitionPanel.setBorder(new TitledBorder(txt))); prefixRepetitionPanel.setLayout(new GridBagLayout()); final var prefixRule = this.getPresenterPrefixRule() @@ -494,7 +525,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // prefix rules final ButtonGroup prefixRuleButtons = new ButtonGroup(); - final JRadioButton noRepetition = new JRadioButton("No Repetition"); + final JRadioButton noRepetition = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.no", + noRepetition::setText); if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) { noRepetition.setSelected(true); } @@ -507,7 +540,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton noRestriction = new JRadioButton("No Restriction"); + final JRadioButton noRestriction = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.any", + noRestriction::setText); if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) { noRestriction.setSelected(true); } @@ -520,8 +555,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton customRepetition = new JRadioButton( - "Complex Repetition"); + final JRadioButton customRepetition = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.complex", + customRepetition::setText); if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) { customRepetition.setSelected(true); } @@ -539,7 +575,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { { final JPanel searchingPanel = new JPanel(); settingsPanel.add(searchingPanel); - searchingPanel.setBorder(new TitledBorder("Search Settings")); + this.localizedTextSetters.put("tv.settings.search.title", + txt -> searchingPanel.setBorder(new TitledBorder(txt))); searchingPanel.setLayout(new GridBagLayout()); // searching rules @@ -547,8 +584,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { final var searchRule = this.presenter.getSearchRule(); - final JRadioButton noPrefixes = new JRadioButton( - "Never Include Prefixed Units"); + final JRadioButton noPrefixes = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.no_prefixes", + noPrefixes::setText); noPrefixes.addActionListener(e -> { this.presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES); this.presenter.updateView(); @@ -558,8 +596,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton commonPrefixes = new JRadioButton( - "Include Common Prefixes"); + final JRadioButton commonPrefixes = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.common_prefixes", + commonPrefixes::setText); commonPrefixes.addActionListener(e -> { this.presenter.setSearchRule(PrefixSearchRule.COMMON_PREFIXES); this.presenter.updateView(); @@ -569,8 +608,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { searchingPanel.add(commonPrefixes, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton alwaysInclude = new JRadioButton( - "Include All Single Prefixes"); + final JRadioButton alwaysInclude = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.all_prefixes", + alwaysInclude::setText); alwaysInclude.addActionListener(e -> { this.presenter .setSearchRule(this.presenter.getUniversalSearchRule()); @@ -599,7 +639,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { settingsPanel.add(miscPanel); miscPanel.setLayout(new GridBagLayout()); - final JCheckBox oneWay = new JCheckBox("Convert One Way Only"); + final JCheckBox oneWay = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.oneway", oneWay::setText); oneWay.setSelected(this.presenter.oneWayConversionEnabled()); oneWay.addItemListener(e -> { this.presenter.setOneWayConversionEnabled( @@ -609,8 +650,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(oneWay, new GridBagBuilder(0, 0, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JCheckBox showAllVariations = new JCheckBox( - "Show Duplicate Units & Prefixes"); + final JCheckBox showAllVariations = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.show_duplicate", + showAllVariations::setText); showAllVariations.setSelected(this.presenter.duplicatesShown()); showAllVariations.addItemListener(e -> { this.presenter @@ -620,7 +662,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(showAllVariations, new GridBagBuilder(0, 1, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JLabel localeLabel = new JLabel("Locale:"); + final JLabel localeLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.locale", + localeLabel::setText); miscPanel.add(localeLabel, new GridBagBuilder(0, 2, 1, 1) .setAnchor(GridBagConstraints.LINE_START).build()); @@ -634,7 +678,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(localeSelector, new GridBagBuilder(1, 2, 1, 1) .setAnchor(GridBagConstraints.LINE_END).build()); - final JButton unitFileButton = new JButton("Manage Unit Data Files"); + final JButton unitFileButton = new JButton(); + this.localizedTextSetters.put("tv.settings.unitfiles.button", + unitFileButton::setText); unitFileButton.setEnabled(false); miscPanel.add(unitFileButton, new GridBagBuilder(0, 3, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); @@ -849,5 +895,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { public void updateText() { this.frame.setTitle(this.presenter.getLocalizedText("tv.title") .replace("[v]", ProgramInfo.VERSION.toString())); + this.localizedTextSetters.forEach((id, action) -> + action.accept(this.presenter.getLocalizedText(id))); } } diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt index 9d453c9..19ed781 100644 --- a/src/main/resources/locales/en.txt +++ b/src/main/resources/locales/en.txt @@ -1 +1,29 @@ -tv.title=7Units [v] \ No newline at end of file +tv.title=7Units [v] +tv.convert_units.title=Convert Units +tv.convert_units.value_prompt=Value to convert: +tv.convert_units.convert_btn=Convert +tv.convert_expressions.title=Convert Unit Expressions +tv.convert_expressions.from=From +tv.convert_expressions.to=To +tv.convert_expressions.convert_btn=Convert +tv.convert_expressions.output=Output +tv.unit_viewer.title=Unit Viewer +tv.prefix_viewer.title=Prefix Viewer +tv.settings.rounding.title=Rounding Settings +tv.settings.rounding.rule=Rounding Rule: +tv.settings.rounding.precision=Precision: +tv.settings.rounding.fixed_sigfig=Fixed Precision +tv.settings.rounding.fixed_places=Fixed Decimal Places +tv.settings.rounding.uncertainty=Uncertainty-based Rounding +tv.settings.repetition.title=Prefix Repetition Settings +tv.settings.repetition.no=No Repetition +tv.settings.repetition.any=No Restriction +tv.settings.repetition.complex=Complex Repetition +tv.settings.search.title=Search Settings +tv.settings.search.no_prefixes=Never Include Prefixed Units +tv.settings.search.common_prefixes=Include Common Prefixes +tv.settings.search.all_prefixes=Include All Single Prefixes +tv.settings.oneway=Convert One Way Only +tv.settings.show_duplicate=Show Duplicate Units & Prefixes +tv.settings.locale=🌐 Locale: +tv.settings.unitfiles.button=Manage Unit Data Files -- cgit v1.2.3 From 1007169658004c78c408f8bd1f4efbbeb6448323 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 18:14:53 -0500 Subject: Complete French locale translation --- src/main/resources/locales/fr.txt | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt index 1d08fda..d25e2b0 100644 --- a/src/main/resources/locales/fr.txt +++ b/src/main/resources/locales/fr.txt @@ -1 +1,29 @@ -tv.title=7Unités [v] \ No newline at end of file +tv.title=7Unités [v] +tv.convert_units.title=Convertir Unités +tv.convert_units.value_prompt=Valueur à convertir: +tv.convert_units.convert_btn=Convertir +tv.convert_expressions.title=Convertir Expressions +tv.convert_expressions.from=De +tv.convert_expressions.to=À +tv.convert_expressions.convert_btn=Convertir +tv.convert_expressions.output=Résultat +tv.unit_viewer.title=Unités +tv.prefix_viewer.title=Préfixes +tv.settings.rounding.title=Préférences d’Arrondissement +tv.settings.rounding.rule=Règle d’Arrondissement +tv.settings.rounding.precision=Précision: +tv.settings.rounding.fixed_sigfig=Precision fixe +tv.settings.rounding.fixed_places=Chiffres fixe +tv.settings.rounding.uncertainty=Arrondissement d’Incertitude +tv.settings.repetition.title=Préférences de répetition de préfixes +tv.settings.repetition.no=Non répetition +tv.settings.repetition.any=Tout répetition +tv.settings.repetition.complex=Répetition Complexe +tv.settings.search.title=Préférences de Recherche +tv.settings.search.no_prefixes=Jamais inclure unités préfixés +tv.settings.search.common_prefixes=Inclure préfixes fréquents +tv.settings.search.all_prefixes=Inclure tous préfixes seuls +tv.settings.oneway=Convertir Seulement en un Direction +tv.settings.show_duplicate=Montrer unités et préfixes doubles +tv.settings.locale=🌐 Locale: +tv.settings.unitfiles.button=Gérer donées d’unités -- cgit v1.2.3 From 4436b29053a0b757562ecc1d0a78e22902e6e5ae Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 19:20:30 -0500 Subject: Allow default datafile to be disabled If this option is deselected, the default unit, prefix, dimension and metric exception data will not be loaded, and only custom data and the few units that are not provided by files will be available. The main rationale for this change is so that the data can be localized by custom unit files. --- src/main/java/sevenUnitsGUI/Presenter.java | 93 +++++++++++++++++++++----- src/main/java/sevenUnitsGUI/TabbedView.java | 18 ++++- src/main/resources/locales/en.txt | 1 + src/main/resources/locales/fr.txt | 1 + src/test/java/sevenUnitsGUI/PresenterTest.java | 18 +++++ 5 files changed, 110 insertions(+), 21 deletions(-) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 6467f03..ba600e3 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -347,6 +347,19 @@ public final class Presenter { * unit view in views that show units as a list to choose from. */ private boolean showDuplicates = false; + + /** + * The default unit, prefix, dimension and exception data will only be loaded + * if this variable is true. + */ + private boolean useDefaultDatafiles = true; + + /** Custom unit datafiles that will be loaded by {@link #reloadData} */ + private final Set customUnitFiles = new HashSet<>(); + /** Custom dimension datafiles that will be loaded by {@link #reloadData} */ + private final Set customDimensionFiles = new HashSet<>(); + /** Custom exception datafiles that will be loaded by {@link #reloadData} */ + private final Set customExceptionFiles = new HashSet<>(); /** * Creates a Presenter @@ -357,8 +370,45 @@ public final class Presenter { public Presenter(View view) { this.view = view; this.database = new UnitDatabase(); - addDefaults(this.database); + this.metricExceptions = new HashSet<>(); + + this.locales = this.loadLocales(); + this.userLocale = DEFAULT_LOCALE; + + // set default settings temporarily + if (Files.exists(CONFIG_FILE)) { + this.loadSettings(CONFIG_FILE); + } + this.reloadData(); + + // print out unit counts + System.out.println(this.loadStatMsg()); + } + + /** + * Clears then reloads all unit, prefix, dimension and exception data. + */ + public void reloadData() { + this.database.clear(); + this.metricExceptions.clear(); + addDefaults(this.database); + + if (this.useDefaultDatafiles) { + this.loadDefaultData(); + } + + this.customUnitFiles.forEach( + path -> this.handleLoadErrors(this.database.loadUnitsFile(path))); + this.customDimensionFiles.forEach( + path -> this.handleLoadErrors(this.database.loadDimensionFile(path))); + this.customExceptionFiles.forEach(this::loadExceptionFile); + } + + /** + * Load units, prefixes and dimensions from the default files. + */ + private void loadDefaultData() { // load units and prefixes try (final var units = inputStream(DEFAULT_UNITS_FILEPATH)) { this.handleLoadErrors(this.database.loadUnitsFromStream(units)); @@ -377,7 +427,6 @@ public final class Presenter { // load metric exceptions try { - this.metricExceptions = new HashSet<>(); try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); var scanner = new Scanner(exceptions)) { while (scanner.hasNextLine()) { @@ -392,17 +441,6 @@ public final class Presenter { throw new AssertionError("Loading of metric_exceptions.txt failed.", e); } - - this.locales = this.loadLocales(); - this.userLocale = DEFAULT_LOCALE; - - // set default settings temporarily - if (Files.exists(CONFIG_FILE)) { - this.loadSettings(CONFIG_FILE); - } - - // print out unit counts - System.out.println(this.loadStatMsg()); } /** @@ -962,6 +1000,10 @@ public final class Presenter { * @since 2021-12-15 */ void loadSettings(Path settingsFile) { + this.customDimensionFiles.clear(); + this.customExceptionFiles.clear(); + this.customUnitFiles.clear(); + for (final Map.Entry setting : this .settingsFromFile(settingsFile)) { final var value = setting.getValue(); @@ -970,15 +1012,13 @@ public final class Presenter { // set manually to avoid the unnecessary saving of the non-manual // methods case "custom_dimension_file": - this.handleLoadErrors( - this.database.loadDimensionFile(pathFromConfig(value))); + this.customDimensionFiles.add(pathFromConfig(value)); break; case "custom_exception_file": - this.loadExceptionFile(pathFromConfig(value)); + this.customExceptionFiles.add(pathFromConfig(value)); break; case "custom_unit_file": - this.handleLoadErrors( - this.database.loadUnitsFile(pathFromConfig(value))); + this.customUnitFiles.add(pathFromConfig(value)); break; case "number_display_rule": this.setDisplayRuleFromString(value); @@ -1204,6 +1244,16 @@ public final class Presenter { return null; } } + + /** + * Sets whether or not the default datafiles will be loaded. + * This method automatically updates the view's units. + */ + public void setUseDefaultDatafiles(boolean useDefaultDatafiles) { + this.useDefaultDatafiles = useDefaultDatafiles; + this.reloadData(); + this.updateView(); + } /** * Sets the user's locale, updating the view. @@ -1304,6 +1354,13 @@ public final class Presenter { ucview.setToUnitNames(toNames); } } + + /** + * @return true iff the default datafiles are being used + */ + public boolean usingDefaultDatafiles() { + return this.useDefaultDatafiles; + } /** * @param message message to add diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index ca9f23c..40ed0a7 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -662,10 +662,22 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(showAllVariations, new GridBagBuilder(0, 1, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); + final JCheckBox useDefaultFiles = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.use_default_files", + useDefaultFiles::setText); + useDefaultFiles.setSelected(this.presenter.usingDefaultDatafiles()); + useDefaultFiles.addItemListener(e -> { + this.presenter + .setUseDefaultDatafiles(e.getStateChange() == ItemEvent.SELECTED); + this.presenter.saveSettings(); + }); + miscPanel.add(useDefaultFiles, new GridBagBuilder(0, 2, 2, 1) + .setAnchor(GridBagConstraints.LINE_START).build()); + final JLabel localeLabel = new JLabel(); this.localizedTextSetters.put("tv.settings.locale", localeLabel::setText); - miscPanel.add(localeLabel, new GridBagBuilder(0, 2, 1, 1) + miscPanel.add(localeLabel, new GridBagBuilder(0, 3, 1, 1) .setAnchor(GridBagConstraints.LINE_START).build()); this.presenter.getAvailableLocales().stream().sorted() @@ -675,14 +687,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.presenter.setUserLocale((String) e.getItem()); this.presenter.saveSettings(); }); - miscPanel.add(localeSelector, new GridBagBuilder(1, 2, 1, 1) + miscPanel.add(localeSelector, new GridBagBuilder(1, 3, 1, 1) .setAnchor(GridBagConstraints.LINE_END).build()); final JButton unitFileButton = new JButton(); this.localizedTextSetters.put("tv.settings.unitfiles.button", unitFileButton::setText); unitFileButton.setEnabled(false); - miscPanel.add(unitFileButton, new GridBagBuilder(0, 3, 2, 1) + miscPanel.add(unitFileButton, new GridBagBuilder(0, 4, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); } diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt index 19ed781..5173cf3 100644 --- a/src/main/resources/locales/en.txt +++ b/src/main/resources/locales/en.txt @@ -25,5 +25,6 @@ tv.settings.search.common_prefixes=Include Common Prefixes tv.settings.search.all_prefixes=Include All Single Prefixes tv.settings.oneway=Convert One Way Only tv.settings.show_duplicate=Show Duplicate Units & Prefixes +tv.settings.use_default_files=Use Default Datafiles tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Manage Unit Data Files diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt index d25e2b0..e8b7138 100644 --- a/src/main/resources/locales/fr.txt +++ b/src/main/resources/locales/fr.txt @@ -25,5 +25,6 @@ tv.settings.search.common_prefixes=Inclure préfixes fréquents tv.settings.search.all_prefixes=Inclure tous préfixes seuls tv.settings.oneway=Convertir Seulement en un Direction tv.settings.show_duplicate=Montrer unités et préfixes doubles +tv.settings.use_default_files=Utilise donées par défaut tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Gérer donées d’unités diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 9e25a08..8b16365 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -17,6 +17,7 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -154,6 +155,23 @@ public final class PresenterTest { expectedOutput.getValue().toString(false, RoundingMode.HALF_EVEN)); assertEquals(List.of(expectedUC), viewBot.unitConversionList()); } + + /** + * Ensures that the default unitfile can be disabled. + * + * @since v1.0.0 + * @since 2025-02-23 + */ + @Test + void testDisableDefault() { + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); + assumeTrue(presenter.database.containsUnitName("joule"), + "Attempted to test disabling default on unit not in default file."); + presenter.setUseDefaultDatafiles(false); + assertFalse(presenter.database.containsUnitName("joule"), + "Presenter disabled default datafiles, but still contains the joule."); + } /** * Tests that duplicate units are successfully removed, if that is asked for -- cgit v1.2.3 From 9c358d708ba4988648d7b19ccb842f076ec4c354 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 20:23:47 -0500 Subject: Allow internationalization of about.txt This works with custom locales (by placing the text in [config_dir]/about/[name].txt), but if such a file does not exist, it will default to the default locale (en)'s about text. --- src/main/java/sevenUnitsGUI/Presenter.java | 43 +++++++++++++++++++++-------- src/main/java/sevenUnitsGUI/TabbedView.java | 13 +++++---- src/main/resources/about.txt | 25 ----------------- src/main/resources/about/en.txt | 25 +++++++++++++++++ src/main/resources/about/fr.txt | 24 ++++++++++++++++ src/main/resources/locales/en.txt | 1 + src/main/resources/locales/fr.txt | 1 + 7 files changed, 90 insertions(+), 42 deletions(-) delete mode 100644 src/main/resources/about.txt create mode 100644 src/main/resources/about/en.txt create mode 100644 src/main/resources/about/fr.txt diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index ba600e3..3a039a7 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -780,7 +780,27 @@ public final class Presenter { * @since 2022-02-19 */ public String getAboutText() { - return Presenter.getLinesFromResource("/about.txt").stream() + final Path customFilepath = Presenter.pathFromConfig( + "about/" + this.userLocale + ".txt"); + if (Files.exists(customFilepath)) { + try { + return formatAboutText(Files.lines(customFilepath)); + } catch (IOException e) { + final String filename = String.format("/about/%s.txt", this.userLocale); + return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + } + } else if (LOCAL_LOCALES.contains(this.userLocale)) { + final String filename = String.format("/about/%s.txt", this.userLocale); + return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + } else { + final String filename = String.format("/about/%s.txt", DEFAULT_LOCALE); + return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + } + + } + + private String formatAboutText(Stream rawLines) { + return rawLines .map(Presenter::withoutComments).collect(Collectors.joining("\n")) .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); @@ -1057,16 +1077,17 @@ public final class Presenter { * @since 2024-08-22 */ private String loadStatMsg() { - return String.format( - "Successfully loaded %d unique units with %d names (%d base units), %d unique prefixes with %d names, %d unit sets, and %d named dimensions.", - this.database.unitMapPrefixless(false).size(), - this.database.unitMapPrefixless(true).size(), - this.database.unitMapPrefixless(false).values().stream() - .filter(IS_FULL_BASE).count(), - this.database.prefixMap(false).size(), - this.database.prefixMap(true).size(), - this.database.unitSetMap().size(), - this.database.dimensionMap().size()); + return this.getLocalizedText("load_stat_msg") + .replace("[u]", Integer.toString( + this.database.unitMapPrefixless(false).size())) + .replace("[un]", Integer.toString( + this.database.unitMapPrefixless(true).size())) + .replace("[b]", Long.toString(this.database.unitMapPrefixless(false) + .values().stream().filter(IS_FULL_BASE).count())) + .replace("[p]", Integer.toString(this.database.prefixMap(false).size())) + .replace("[pn]", Integer.toString(this.database.prefixMap(true).size())) + .replace("[s]", Integer.toString(this.database.unitSetMap().size())) + .replace("[d]", Integer.toString(this.database.dimensionMap().size())); } /** diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 40ed0a7..9850aac 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -198,7 +198,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** The text box for prefix data in the prefix viewer */ private final JTextArea prefixTextBox; - // SETTINGS STUFF + // INFO & SETTINGS STUFF + final JTextArea infoTextArea; private final JComboBox localeSelector; private StandardRoundingType roundingType; private int precision; @@ -377,11 +378,10 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { 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(this.presenter.getAboutText()); + this.infoTextArea = new JTextArea(); + this.infoTextArea.setEditable(false); + this.infoTextArea.setOpaque(false); + infoPanel.add(this.infoTextArea); // ============ SETTINGS PANEL ============ this.localeSelector = new JComboBox<>(); @@ -907,6 +907,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { public void updateText() { this.frame.setTitle(this.presenter.getLocalizedText("tv.title") .replace("[v]", ProgramInfo.VERSION.toString())); + this.infoTextArea.setText(this.presenter.getAboutText()); this.localizedTextSetters.forEach((id, action) -> action.accept(this.presenter.getLocalizedText(id))); } diff --git a/src/main/resources/about.txt b/src/main/resources/about.txt deleted file mode 100644 index 4c33f8c..0000000 --- a/src/main/resources/about.txt +++ /dev/null @@ -1,25 +0,0 @@ -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 -. - -Unit/Prefix/Dimension Statistics: - -[LOADSTATS] - -Copyright Notice: - -Unit Converter Copyright (C) 2018-2024 Adrien Hopkins -This program comes with ABSOLUTELY NO WARRANTY; -for details read the LICENSE file, section 15 - -This is free software, and you are welcome to redistribute -it under certain conditions; for details go to - -or read the LICENSE file. diff --git a/src/main/resources/about/en.txt b/src/main/resources/about/en.txt new file mode 100644 index 0000000..068f922 --- /dev/null +++ b/src/main/resources/about/en.txt @@ -0,0 +1,25 @@ +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 +. + +Unit/Prefix/Dimension Statistics: + +[LOADSTATS] + +Copyright Notice: + +Unit Converter Copyright (C) 2018-2025 Adrien Hopkins +This program comes with ABSOLUTELY NO WARRANTY; +for details read the LICENSE file, section 15 + +This is free software, and you are welcome to redistribute +it under certain conditions; for details go to + +or read the LICENSE file. diff --git a/src/main/resources/about/fr.txt b/src/main/resources/about/fr.txt new file mode 100644 index 0000000..d8d82aa --- /dev/null +++ b/src/main/resources/about/fr.txt @@ -0,0 +1,24 @@ +À propos de 7Unités version [VERSION] + +7Unités est une programme pour convertir les unités avec plusieurs fonctions, +inspiré par GNU Units (https://www.gnu.org/software/units/). +Vous pouvez l’utiliser pour convertir des unités, mais vous pouvez aussi +l’utiliser comme calculatrice, computer et convertir des expressions +comme "10 m/s + (25^2 - 5^2) mi/hr". + +Ce logiciel est par Adrien Hopkins . + +Statistiques d’unités, préfixes et dimensions: + +[LOADSTATS] + +Copyright Notice: + +Unit Converter Copyright (C) 2018-2025 Adrien Hopkins +This program comes with ABSOLUTELY NO WARRANTY; +for details read the LICENSE file, section 15 + +This is free software, and you are welcome to redistribute +it under certain conditions; for details go to + +or read the LICENSE file. diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt index 5173cf3..666e363 100644 --- a/src/main/resources/locales/en.txt +++ b/src/main/resources/locales/en.txt @@ -28,3 +28,4 @@ tv.settings.show_duplicate=Show Duplicate Units & Prefixes tv.settings.use_default_files=Use Default Datafiles tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Manage Unit Data Files +load_stat_msg=Successfully loaded [u] unique units with [un] names ([b] base units), [p] unique prefixes with [pn] names, [s] unit sets, and [d] named dimensions. diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt index e8b7138..3fef030 100644 --- a/src/main/resources/locales/fr.txt +++ b/src/main/resources/locales/fr.txt @@ -28,3 +28,4 @@ tv.settings.show_duplicate=Montrer unités et préfixes doubles tv.settings.use_default_files=Utilise donées par défaut tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Gérer donées d’unités +load_stat_msg=Chargé [u] unités uniques avec [un] noms ([b] unités bases), [p] préfixes uniques avec [pn] noms, [s] collections d’unités, et [d] dimensions nomées. -- cgit v1.2.3 From c2b59645f9c3b7eb2db39f8e1cea64a06e41e8fe Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 20:48:08 -0500 Subject: Gracefully handle unidentified locales in config --- src/main/java/sevenUnitsGUI/Presenter.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 3a039a7..0dc7ea0 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -1058,7 +1058,15 @@ public final class Presenter { this.setSearchRuleFromString(value); break; case "locale": - this.userLocale = value; + if (this.locales.containsKey(value)) { + this.userLocale = value; + } else { + System.err.printf( + "Warning: unrecognized locale \"%s\".%n", value); + this.view.showErrorMessage("Unrecognized Locale", + "Could not find locale \"" + value + + "\", resetting to default."); + } break; default: System.err.printf("Warning: unrecognized setting \"%s\".%n", -- cgit v1.2.3 From dfdcf58c8751db95f024528aa38dd81eb2364f39 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 20:53:06 -0500 Subject: Bump version number to 1.0.0b1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compared to version 0.5.0, this release: - allows conversion to sums of units (e.g. 4/3 ft → 1 ft + 4 in) - allows non-integer exponents in expressions - adds the ability to change the UI language - gracefully handles datafile errors - adds more information to the loading-success message, and adds it to the About tab - allows the user to not use the default datafiles No new features will be added until the release of version 1.0.0. --- CHANGELOG.org | 3 +++ README.org | 2 +- docs/roadmap.org | 9 --------- src/main/java/sevenUnits/ProgramInfo.java | 4 ++-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.org b/CHANGELOG.org index 78bb9a1..0798904 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -5,7 +5,10 @@ All notable changes in this project will be shown in this file. - *Allowed conversion to a sum of units (e.g. 4/3 ft \rightarrow 1 ft + 4 in).* - *Allowed exponents on units to be non-integer numbers.* The resulting exponents are rounded to the nearest integer, and the user is warned if this rounding changes the value by more than normal floating-point error. +- *Added the ability to change the language of 7Units's UI.* + This does not affect the names of units, prefixes and dimensions. - Added more information to the loading-success message, and added it to the about tab. +- Added the ability to not use the default data files. *** Changed - *Errors in unit/dimension files are shown in popups, rather than crashing the program.* *** Fixed diff --git a/README.org b/README.org index cf8de11..279c378 100644 --- a/README.org +++ b/README.org @@ -1,4 +1,4 @@ -* 7Units Version 1.0.0-alpha.1 +* 7Units Version 1.0.0-beta.1 (this project uses Semantic Versioning) ** What is it? This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in. diff --git a/docs/roadmap.org b/docs/roadmap.org index 04ea0a5..963c17d 100644 --- a/docs/roadmap.org +++ b/docs/roadmap.org @@ -3,15 +3,6 @@ Here is a list of the unfinished requirements for version 1.0.0. When everythin These requirements are subject to change. I intend to finish version 1.0.0 by [2025-04-27 Sun]. -Feature Requirements: -- 7Units should be able to parse unit files from [[https://www.gnu.org/software/units/][GNU Units]], the program that inspired it. -- 7Units's expression converter should support most or all of the conversion features supported by GNU Units: - - (/Mostly Done/) Converting to sums of units (it should also be possible to do this in the unit converter with preset combinations) - - (/Optional/) Inverse nonlinear conversion with the tilde prefix - - (/Optional/) Nonlinear units should be specifiable in unit files. - - /Any other feature not listed should be considered optional./ -- (/Optional/) It should be possible to add, edit and remove units and prefixes from the GUI unit and prefix viewers. - Documentation/Testing Requirements: - 7Units should be fully documented. - 7Units should have automated testing with a code coverage of at least 2/3 (ideally at least 5/6). diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index 573c5c7..fee3cea 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -26,9 +26,9 @@ import sevenUnits.utils.SemanticVersionNumber; */ public final class ProgramInfo { - /** The version number (1.0.0-alpha.1) */ + /** The version number (1.0.0-beta.1) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber - .preRelease(1, 0, 0, "alpha", 1); + .preRelease(1, 0, 0, "beta", 1); private ProgramInfo() { // this class is only for static variables, you shouldn't be able to -- cgit v1.2.3 From deb4a650caf9eb49d32810e573f22bbd77f0ca47 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 7 Mar 2025 11:13:50 -0500 Subject: Add tests for LinearUnitValue I specifically added tests for the methods that Jacoco says haven't been tested: - convertToMultiple - equals - equivalent - hashCode - operation methods --- src/test/java/sevenUnits/unit/UnitTest.java | 74 -------- src/test/java/sevenUnits/unit/UnitValueTest.java | 214 +++++++++++++++++++++++ 2 files changed, 214 insertions(+), 74 deletions(-) create mode 100644 src/test/java/sevenUnits/unit/UnitValueTest.java diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index 4fb26a3..6ac0ebd 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.math.RoundingMode; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; @@ -29,7 +28,6 @@ import org.junit.jupiter.api.Test; import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.NameSymbol; -import sevenUnits.utils.UncertainDouble; /** * Testing the various Unit classes. This is NOT part of this program's public @@ -153,76 +151,4 @@ class UnitTest { assertEquals(generatedKilometre, actualKilometre); } - - /** - * Tests converting an uncertain LinearUnitValue to a string. - * - * @since 2021-11-04 - */ - @Test - public void testValueToString1() { - final LinearUnitValue value = LinearUnitValue.of(Metric.METRE, - UncertainDouble.of(10, 0.24)); - - assertEquals("(10.0 ± 0.2) m", value.toString()); - assertEquals("(10.0 ± 0.2) m", - value.toString(true, RoundingMode.HALF_EVEN)); - assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN)); - } - - /** - * Tests converting a certain LinearUnitValue to a string. - * - * @since 2021-11-04 - */ - @Test - public void testValueToString2() { - final LinearUnitValue value = LinearUnitValue.of(Metric.METRE, - UncertainDouble.of(10, 0)); - - assertEquals("10.0 m", value.toString()); - assertEquals("(10.0 ± 0.0) m", - value.toString(true, RoundingMode.HALF_EVEN)); - assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN)); - } - - /** - * Tests converting an unnamed LinearUnitValue to a string. - * - * @since 2021-11-04 - */ - @Test - public void testValueToString3() { - final LinearUnitValue value = LinearUnitValue.of( - Metric.METRE.withName(NameSymbol.EMPTY), - UncertainDouble.of(10, 0.24)); - - assertEquals("10.0 unnamed unit (= 10.0 m)", - value.toString(false, RoundingMode.HALF_EVEN)); - } - - /** - * Tests converting a named UnitValue to a string. - * - * @since 2021-11-04 - */ - @Test - public void testValueToString4() { - final UnitValue value = UnitValue.of(BritishImperial.FAHRENHEIT, 80); - - assertEquals("80.0 \u00B0F", value.toString()); - } - - /** - * Tests converting an unnamed UnitValue to a string. - * - * @since 2021-11-04 - */ - @Test - public void testValueToString5() { - final UnitValue value = UnitValue - .of(USCustomary.FAHRENHEIT.withName(NameSymbol.EMPTY), 50); - - assertEquals("50.0 unnamed unit (= 283.15 K)", value.toString()); - } } diff --git a/src/test/java/sevenUnits/unit/UnitValueTest.java b/src/test/java/sevenUnits/unit/UnitValueTest.java new file mode 100644 index 0000000..69569ae --- /dev/null +++ b/src/test/java/sevenUnits/unit/UnitValueTest.java @@ -0,0 +1,214 @@ +package sevenUnits.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.RoundingMode; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import sevenUnits.utils.NameSymbol; +import sevenUnits.utils.UncertainDouble; + +public final class UnitValueTest { + private static Stream testConvertToMultiple() { + return Stream.of( + Arguments.of( + LinearUnitValue.getExact(BritishImperial.Length.INCH, 64), + List.of(BritishImperial.Length.FOOT, + BritishImperial.Length.INCH), + List.of(5.0, 4.0)), + Arguments.of(LinearUnitValue.getExact(Metric.SECOND, 44102.5), + List.of(Metric.HOUR, Metric.MINUTE, Metric.SECOND), + List.of(12.0, 15.0, 2.5))); + } + + @ParameterizedTest + @MethodSource + public void testConvertToMultiple(LinearUnitValue valFrom, + List others, List expectedValues) { + final var actual = valFrom.convertToMultiple(others); + assertEquals(others.size(), expectedValues.size(), String.format( + "%s converted to %s had the wrong number of resulting units (expected: %d, actual: %d).", + valFrom, others, others.size(), actual.size())); + + for (var i = 0; i < others.size(); i++) { + final var expectedValue = LinearUnitValue.getExact(others.get(i), + expectedValues.get(i)); + assertTrue(expectedValue.equivalent(actual.get(i)), + String.format("Value %d: expected %s, actual %s.", i, + expectedValue, actual.get(i))); + } + } + + @Test + public void testConvertToMultipleErrors() { + assertThrows(IllegalArgumentException.class, + () -> LinearUnitValue.ONE.convertToMultiple(List.of())); + assertThrows(IllegalArgumentException.class, + () -> LinearUnitValue.ONE.convertToMultiple(List.of(Metric.METRE))); + } + + // I am intentionally testing whether or not equals gracefully returns false + // when it gets an argument of the wrong type + @SuppressWarnings("unlikely-arg-type") + @Test + public void testEqualsHashCode() { + final var v1 = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(1.0, 0.01)); + final var v2 = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(1.0, 0.01)); + final var v3 = LinearUnitValue.of(Metric.MILLIMETRE, + UncertainDouble.of(1000.0, 10.0)); + final var v4 = LinearUnitValue.of(Metric.MILLIMETRE, + UncertainDouble.of(1000.0, 20.0)); + + assertTrue(Objects.equals(v1, v2), + "Two identical values are not equal()"); + assertTrue(Objects.equals(v1.hashCode(), v2.hashCode()), + "Two identical values have different hash codes."); + assertTrue(Objects.equals(v1, v3), + "Two values with the same value but different units are not equal()"); + assertTrue(Objects.equals(v1.hashCode(), v3.hashCode()), + "Two values with the same value but different units have different hash codes."); + assertFalse(Objects.equals(v1, v4), + "Two values with the same value but different uncertainties are equal()"); + assertFalse(Objects.equals(v1, "Hello")); + assertFalse(v1.equals("Hello", true)); + } + + @Test + public void testEqualsNoFP() { + final var v1 = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(1.0, 0.01)); + final var v2 = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(1.0, 0.01)); + final var v3 = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(1.0 + 5e-16, 0.01)); + assertTrue(v1.equals(v2, false)); + assertFalse(v1.equals(v3, false)); + assertTrue(v1.equals(v3, true)); + } + + @Test + public void testOperations() { + final var v1 = LinearUnitValue.getExact(Metric.METRE, 2.0); + final var v2 = LinearUnitValue.getExact(Metric.METRE, 4.0); + + assertEquals(v2, v1.times(2)); + assertEquals(v1, v2.dividedBy(2)); + assertEquals(LinearUnitValue.getExact(Metric.SQUARE_METRE, 8.0), + v1.times(v2)); + assertEquals(LinearUnitValue.getExact(Metric.ONE, 0.5), v1.dividedBy(v2)); + assertEquals(LinearUnitValue.getExact(Metric.SQUARE_METRE, 4.0), + v1.toExponent(2)); + assertEquals(LinearUnitValue.getExact(Metric.SQUARE_METRE, 4.0), + v1.toExponentRounded(2.0)); + } + + @Test + public void testValueEquivalent() { + final var v1 = LinearUnitValue.of(BritishImperial.Length.INCH, + UncertainDouble.of(1.0, 0.0)); + final var v2 = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(0.0255, 0.0)); + assertFalse(v1.equivalent(v2)); + + final var v3 = LinearUnitValue.of(BritishImperial.Length.INCH, + UncertainDouble.of(1.0, 1.0)); + final var v4 = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(0.0255, 0.025)); + assertTrue(v3.equivalent(v4)); + } + + @Test + public void testValueEquivalentErrors() { + final var v1 = LinearUnitValue.of(BritishImperial.Length.INCH, + UncertainDouble.of(1.0, 0.0)); + final var v2 = LinearUnitValue.of(BritishImperial.CALORIE, + UncertainDouble.of(1.0, 0.0)); + assertFalse(v1.equivalent(v2)); + + assertFalse(v1.equivalent(null)); + } + + /** + * Tests converting an uncertain LinearUnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString1() { + final var value = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(10, 0.24)); + + assertEquals("(10.0 ± 0.2) m", value.toString()); + assertEquals("(10.0 ± 0.2) m", + value.toString(true, RoundingMode.HALF_EVEN)); + assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN)); + } + + /** + * Tests converting a certain LinearUnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString2() { + final var value = LinearUnitValue.of(Metric.METRE, + UncertainDouble.of(10, 0)); + + assertEquals("10.0 m", value.toString()); + assertEquals("(10.0 ± 0.0) m", + value.toString(true, RoundingMode.HALF_EVEN)); + assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN)); + } + + /** + * Tests converting an unnamed LinearUnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString3() { + final var value = LinearUnitValue.of( + Metric.METRE.withName(NameSymbol.EMPTY), + UncertainDouble.of(10, 0.24)); + + assertEquals("10.0 unnamed unit (= 10.0 m)", + value.toString(false, RoundingMode.HALF_EVEN)); + } + + /** + * Tests converting a named UnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString4() { + final var value = UnitValue.of(BritishImperial.FAHRENHEIT, 80); + + assertEquals("80.0 \u00B0F", value.toString()); + } + + /** + * Tests converting an unnamed UnitValue to a string. + * + * @since 2021-11-04 + */ + @Test + public void testValueToString5() { + final var value = UnitValue + .of(USCustomary.FAHRENHEIT.withName(NameSymbol.EMPTY), 50); + + assertEquals("50.0 unnamed unit (= 283.15 K)", value.toString()); + } +} -- cgit v1.2.3 From 7257fbd189c779105c9393458a2e0cdf06e285f7 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 7 Mar 2025 11:23:29 -0500 Subject: convertToMulti: Compare unit bases to this.base This is a necessary precondition for the conversion methods used later on in convertToMutliple, and it is simpler than the existing method. I can also be sure it works, due to the tests I just wrote. --- src/main/java/sevenUnits/unit/LinearUnitValue.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index a8848fc..e239b49 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -126,11 +126,10 @@ public final class LinearUnitValue { final List others) { if (others.size() < 1) throw new IllegalArgumentException("Must have at least one unit"); - final ObjectProduct unitBase = others.get(0).getBase(); for (final LinearUnit unit : others) { - if (!unitBase.equals(unit.getBase())) + if (!Objects.equals(this.unit.getBase(), unit.getBase())) throw new IllegalArgumentException( - "All units must have the same base."); + "All provided units must have the same base as the value."); } LinearUnitValue remaining = this; -- cgit v1.2.3 From bf24568419073c15143b5f59d131751dc73505b3 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 7 Mar 2025 13:21:04 -0500 Subject: Add javadoc option to gradle I am not adding it to build yet since my javadoc has problems, but I will once those are fixed. --- build.gradle | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 8b90060..b775b70 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { - id "java" - id "application" - id "jacoco" + id "java" + id "application" + id "jacoco" } java { @@ -11,13 +11,13 @@ java { mainClassName = "sevenUnitsGUI.Main" repositories { - mavenCentral() + mavenCentral() } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' } jar { @@ -42,5 +42,9 @@ jacocoTestReport { dependsOn test } +javadoc { + destinationDir = file("${buildDir}/docs/javadoc") +} + run { } -- cgit v1.2.3 From 99ef729b27377b440b15757b789101c7ed87aec1 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 21 Mar 2025 15:26:01 -0500 Subject: Add tests for NameSymbol --- src/main/java/sevenUnits/utils/NameSymbol.java | 12 ++-- src/test/java/sevenUnits/utils/NameSymbolTest.java | 73 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 src/test/java/sevenUnits/utils/NameSymbolTest.java diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 49c44fa..290dcd6 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -196,14 +196,18 @@ public final class NameSymbol { * of the argument * @since 2019-10-21 */ - private NameSymbol(final Optional primaryName, + NameSymbol(final Optional primaryName, final Optional symbol, final Set otherNames) { this.primaryName = primaryName; this.symbol = symbol; - otherNames.remove(null); - this.otherNames = Collections.unmodifiableSet(otherNames); + if (otherNames != null) { + otherNames.remove(null); + this.otherNames = Collections.unmodifiableSet(otherNames); + } else { + this.otherNames = Set.of(); + } - if (this.primaryName.isEmpty()) { + if (this.primaryName == null || this.primaryName.isEmpty()) { assert this.otherNames.isEmpty(); } } diff --git a/src/test/java/sevenUnits/utils/NameSymbolTest.java b/src/test/java/sevenUnits/utils/NameSymbolTest.java new file mode 100644 index 0000000..327cd9f --- /dev/null +++ b/src/test/java/sevenUnits/utils/NameSymbolTest.java @@ -0,0 +1,73 @@ +package sevenUnits.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class NameSymbolTest { + private static Stream testEqualsHashCode() { + return Stream.of( + Arguments.of(NameSymbol.ofName("test"), NameSymbol.ofName("test"), true), + Arguments.of(NameSymbol.ofName("a"), NameSymbol.ofName("b"), false), + Arguments.of(NameSymbol.ofSymbol("test"), NameSymbol.ofSymbol("test"), true), + Arguments.of(NameSymbol.ofSymbol("a"), NameSymbol.ofSymbol("b"), false), + Arguments.of(NameSymbol.ofName("test"), NameSymbol.ofSymbol("test"), false), + Arguments.of(NameSymbol.of("main", "s"), NameSymbol.of("main", "s"), true), + Arguments.of(NameSymbol.of("main", "s"), NameSymbol.of("main", "s", "m"), false), + Arguments.of(new NameSymbol(null, null, new HashSet<>()), + new NameSymbol(null, null, new HashSet<>()), true), + Arguments.of(new NameSymbol( + Optional.of("main"), Optional.of("s"), new HashSet<>()), + new NameSymbol(null, null, new HashSet<>()), false), + Arguments.of(new NameSymbol(null, null, new HashSet<>()), + new NameSymbol(Optional.of("main"), Optional.of("s"), + new HashSet<>()), false), + Arguments.of(new NameSymbol(Optional.of("main"), null, new HashSet<>()), + new NameSymbol(Optional.of("main"), Optional.of("s"), + new HashSet<>()), false)); + } + + /** + * Tests that two NameSymbols are or are not equal. + * If they are equal, also ensures they have the same hash code. + * @param a first NameSymbol to test + * @param b second NameSymbol to test + * @param equal true iff a should be equal to be, otherwise false + */ + @ParameterizedTest + @MethodSource + public void testEqualsHashCode(NameSymbol a, NameSymbol b, boolean equal) { + if (equal) { + assertTrue(Objects.equals(a, b)); + assertEquals(a.hashCode(), b.hashCode(), + "Equal NameSymbol instances have different hash codes."); + } else { + assertFalse(Objects.equals(a, b)); + } + } + + @Test + public void testCreate() { + Set names = Set.of("a", "b", "c"); + NameSymbol ns = NameSymbol.ofNullable(null, null, names); + assertTrue(ns.getPrimaryName().isPresent(), "NameSymbol created without primary name."); + assertTrue(names.contains(ns.getPrimaryName().orElseThrow()), + String.format("Primary name (%s) was not obtained from names set.", + ns.getPrimaryName())); + assertFalse(ns.getOtherNames().contains(ns.getPrimaryName().orElseThrow()), + String.format("Primary name (%s) was included in other names set.", + ns.getPrimaryName())); + assertEquals(Set.of("a", "b", "c"), names, "names input was changed by ofNullable()"); + } +} -- cgit v1.2.3 From d1335dd378a7e302e4853c2695f2931c244b8863 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 20 Apr 2025 18:23:31 -0500 Subject: Improve Metric base units SuppressWarnings message --- src/main/java/sevenUnits/unit/Metric.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 7841987..2fc3928 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -67,8 +67,10 @@ public final class Metric { } /// base units of the SI - // suppressing warnings since these are the same object, but in a different - /// form (class) + // suppressing warnings since these are the same object, + // but in a different form (class) + // and because these objects are only used outside this class, + // where hiding is not a problem. @SuppressWarnings("hiding") public static final class BaseUnits { public static final BaseUnit METRE = BaseUnit -- cgit v1.2.3 From 4910b914392753986526bc28102ddef42e275e6c Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 30 Apr 2025 16:42:01 -0500 Subject: Add more UnitDatabase tests --- src/main/java/sevenUnits/unit/UnitDatabase.java | 13 +- .../java/sevenUnits/unit/UnitDatabaseTest.java | 460 +++++++++++++-------- 2 files changed, 286 insertions(+), 187 deletions(-) diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index dc81aca..690430b 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1905,18 +1905,23 @@ public final class UnitDatabase { * * @since 2024-08-22 */ - private List getUnitSetFromExpression(String expression) { + List getUnitSetFromExpression(String expression) { final String[] parts = expression.split(";"); final List units = new ArrayList<>(parts.length); for (final String unitName : parts) { final Unit unit = this.getUnitFromExpression(unitName.trim()); - if (unit instanceof LinearUnit) { - units.add((LinearUnit) unit); - } else + if (!(unit instanceof LinearUnit)) { throw new IllegalArgumentException(String.format( "Unit '%s' is in a unit-set expression, but is not linear.", unitName)); + } else if (units.size() > 0 && !unit.canConvertTo(units.get(0))) { + throw new IllegalArgumentException(String.format( + "Units in expression '%s' have different dimensions.", + expression)); + } + + units.add((LinearUnit) unit); } return units; } diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index e7f3ccf..e55d6af 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -23,21 +23,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import sevenUnits.utils.NameSymbol; @@ -46,7 +46,7 @@ import sevenUnits.utils.UncertainDouble; /** * A test for the {@link UnitDatabase} class. This is NOT part of this program's * public API. - * + * * @author Adrien Hopkins * @since 2019-04-14 * @since v0.2.0 @@ -54,18 +54,18 @@ import sevenUnits.utils.UncertainDouble; class UnitDatabaseTest { private static final class SimpleEntry implements Map.Entry { private final K key; - + private V value; - + /** - * + * * @since 2021-10-07 */ public SimpleEntry(K key, V value) { this.key = key; this.value = value; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -76,44 +76,44 @@ class UnitDatabaseTest { return Objects.equals(this.key, other.getKey()) && Objects.equals(this.value, other.getValue()); } - + @Override public K getKey() { return this.key; } - + @Override public V getValue() { return this.value; } - + @Override public int hashCode() { return (this.key == null ? 0 : this.key.hashCode()) ^ (this.value == null ? 0 : this.value.hashCode()); } - + @Override public V setValue(V value) { - final V oldValue = this.value; + final var oldValue = this.value; this.value = value; return oldValue; } } - + // some linear units and one nonlinear private static final Unit U = Metric.METRE; private static final Unit V = Metric.KILOGRAM; - + private static final Unit W = Metric.SECOND; // used for testing expressions // J = U^2 * V / W^2 private static final LinearUnit J = Metric.KILOGRAM .times(Metric.METRE.toExponent(2)) .dividedBy(Metric.SECOND.toExponent(2)); - + private static final LinearUnit K = Metric.KELVIN; - + private static final Unit NONLINEAR = Unit.fromConversionFunctions( Metric.METRE.getBase(), o -> o + 1, o -> o - 1); // make the prefix values prime so I can tell which multiplications were made @@ -124,9 +124,9 @@ class UnitDatabaseTest { private static final UnitPrefix C = UnitPrefix.valueOf(5) .withName(NameSymbol.ofName("C")); private static final UnitPrefix AB = UnitPrefix.valueOf(7); - + private static final UnitPrefix BC = UnitPrefix.valueOf(11); - + /** * Gets a map entry. * @@ -140,7 +140,7 @@ class UnitDatabaseTest { private static Map.Entry entry(K key, V value) { return new SimpleEntry<>(key, value); } - + /** * Loads the dimensionfile at src/test/resources/[path] to the database * {@code loadTo}. @@ -152,7 +152,7 @@ class UnitDatabaseTest { */ private static List loadDimensionFile(UnitDatabase loadTo, String path) { - try (final InputStream testFile = UnitDatabaseTest.class + try (final var testFile = UnitDatabaseTest.class .getResourceAsStream(path)) { return loadTo.loadDimensionsFromStream(testFile); } catch (final IOException e) { @@ -160,7 +160,7 @@ class UnitDatabaseTest { return Collections.emptyList(); } } - + /** * Loads the unitfile at src/test/resources/[path] to the database * {@code loadTo}. @@ -172,7 +172,7 @@ class UnitDatabaseTest { */ private static List loadUnitsFile(UnitDatabase loadTo, String path) { - try (final InputStream testFile = UnitDatabaseTest.class + try (final var testFile = UnitDatabaseTest.class .getResourceAsStream(path)) { return loadTo.loadUnitsFromStream(testFile); } catch (final IOException e) { @@ -180,53 +180,92 @@ class UnitDatabaseTest { return Collections.emptyList(); } } - + + private static final Stream testEvaluateExpressionInvalid() { + return Stream.of(Arguments.of("K^K"), Arguments.of("1 + K")); + } + + private static final Stream testEvaluateExpressionValid() { + return Stream.of( + Arguments.of("J + (2 * 3) J + (20 / 4) J", + LinearUnitValue.of(J, + UncertainDouble.of(12, Math.sqrt(14.625)))), + Arguments.of("J - -1 * J", + LinearUnitValue.of(J, UncertainDouble.of(2, 1))), + Arguments.of("K^2", + LinearUnitValue.of(K.times(K), UncertainDouble.of(1, 0)))); + } + + /** + * Tests expressions that are valid to the parser, but semantically invalid + * (e.g. adding different dimensions) + * + * @param expression expression to test - should throw + * {@link IllegalArgumentException} + */ + @ParameterizedTest + @MethodSource + public void testEvaluateExpressionInvalid(String expression) { + final var database = new UnitDatabase(); + + database.addUnit("J", J); + database.addUnit("K", K); + + database.addPrefix("A", A); + database.addPrefix("B", B); + database.addPrefix("C", C); + + assertThrows(IllegalArgumentException.class, + () -> database.getUnitFromExpression(expression)); + assertThrows(IllegalArgumentException.class, + () -> database.evaluateUnitExpression(expression)); + } + /** * A test for the {@link UnitDatabase#evaluateUnitExpression(String)} * function. Simple because the expression parser has its own test. - * + * * @since 2021-09-27 */ - @Test - public void testEvaluateExpression() { - final UnitDatabase database = new UnitDatabase(); - + @ParameterizedTest + @MethodSource + public void testEvaluateExpressionValid(String expression, + LinearUnitValue expected) { + final var database = new UnitDatabase(); + database.addUnit("J", J); database.addUnit("K", K); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - - final LinearUnitValue expected = LinearUnitValue.of(J, - UncertainDouble.of(12, Math.sqrt(14.625))); - // note: units are exact, each number has an uncertainty of 1 - final LinearUnitValue actual = database - .evaluateUnitExpression("J + (2 * 3) J + (20 / 4) J"); + + final var actual = database + .evaluateUnitExpression(expression); assertEquals(expected, actual); - - // check that negation works properly - assertEquals(2, - database.evaluateUnitExpression("J - -1 * J").getValueExact()); + + final var expectedU = expected.getUnit().times(expected.getValueExact()); + final var actualU = database.getUnitFromExpression(expression); + assertEquals(expectedU, actualU); } - + /** * Test for {@link UnitDatabase#getUnit}, {@link UnitDatabase#getLinearUnit} * and {@link UnitDatabase#getLinearUnitValue}. - * + * * @since 2021-10-07 */ @Test public void testGetUnit() { - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("m", Metric.METRE); database.addUnit("meter", Metric.METRE); database.addUnit("metre", Metric.METRE); database.addUnit("badname", Metric.METRE); database.addUnit("K", Metric.KELVIN); database.addUnit("degC", Metric.CELSIUS); - + // ensure getUnit returns units, regardless of whether the name is one of // the unit's names assertEquals(Metric.METRE, database.getUnit("m")); @@ -235,40 +274,40 @@ class UnitDatabaseTest { assertEquals(Metric.METRE, database.getUnit("badname")); assertThrows(NoSuchElementException.class, () -> database.getUnit("blabla")); - + assertEquals(Metric.KELVIN, database.getLinearUnit("K")); assertThrows(IllegalArgumentException.class, () -> database.getLinearUnit("degC")); assertEquals(Metric.KELVIN.times(373.15), database.getLinearUnit("degC(100)")); } - + /** * Confirms that operations that shouldn't function for infinite databases * throw an {@code IllegalStateException}. - * + * * @since 2019-05-03 */ // @Test // @Timeout(value = 1, unit = TimeUnit.SECONDS) public void testInfiniteSetExceptions() { // load units - final UnitDatabase infiniteDatabase = new UnitDatabase(); - + final var infiniteDatabase = new UnitDatabase(); + infiniteDatabase.addUnit("J", J); infiniteDatabase.addUnit("K", K); - + infiniteDatabase.addPrefix("A", A); infiniteDatabase.addPrefix("B", B); infiniteDatabase.addPrefix("C", C); - - final Set> entrySet = infiniteDatabase.unitMap() + + final var entrySet = infiniteDatabase.unitMap() .entrySet(); - final Set keySet = infiniteDatabase.unitMap().keySet(); + final var keySet = infiniteDatabase.unitMap().keySet(); assertThrows(IllegalStateException.class, () -> entrySet.toArray()); assertThrows(IllegalStateException.class, () -> keySet.toArray()); } - + /** * A bunch of tests for invalid dimension files * @@ -278,19 +317,19 @@ class UnitDatabaseTest { @ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) public void testLoadingInvalidDimensionFile(int num) { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addDimension("LENGTH", Metric.Dimensions.LENGTH); database.addDimension("MASS", Metric.Dimensions.MASS); database.addDimension("TIME", Metric.Dimensions.TIME); - final String filename = String.format("/test-dimensionfile-invalid%d.txt", + final var filename = String.format("/test-dimensionfile-invalid%d.txt", num); - final List errs = loadDimensionFile(database, filename); + final var errs = loadDimensionFile(database, filename); assertFalse(errs.isEmpty(), "no error from invalid file " + filename); - final RuntimeException e = errs.get(0).problem(); + final var e = errs.get(0).problem(); assertTrue(e instanceof IllegalArgumentException || e instanceof NoSuchElementException); } - + /** * A bunch of tests for invalid unit files * @@ -300,113 +339,117 @@ class UnitDatabaseTest { @ParameterizedTest @ValueSource(ints = { 1, 2, 3, 4, 5 }) public void testLoadingInvalidUnitFile(int num) { - final UnitDatabase database = new UnitDatabase(); - final String filename = String.format("/test-unitsfile-invalid%d.txt", + final var database = new UnitDatabase(); + final var filename = String.format("/test-unitsfile-invalid%d.txt", num); - final List errs = loadUnitsFile(database, filename); + final var errs = loadUnitsFile(database, filename); assertFalse(errs.isEmpty(), "no error from invalid file " + filename); - final RuntimeException e = errs.get(0).problem(); + final var e = errs.get(0).problem(); assertTrue(e instanceof IllegalArgumentException || e instanceof NoSuchElementException); } - + /** * Tests loading a valid dimension-file with some derived dimensions. - * + * * @since 2021-10-04 */ @Test public void testLoadingValidDimensions() { - final UnitDatabase database = new UnitDatabase(); + final var database = new UnitDatabase(); database.addDimension("LENGTH", Metric.Dimensions.LENGTH); database.addDimension("MASS", Metric.Dimensions.MASS); database.addDimension("TIME", Metric.Dimensions.TIME); - + loadDimensionFile(database, "/test-dimensionfile-valid1.txt"); assertEquals(Metric.Dimensions.ENERGY, database.getDimension("ENERGY")); assertEquals(Metric.Dimensions.POWER, database.getDimension("POWER")); - + } - + /** * Tests loading a valid unitfile with some prefixes and no units. - * + * * @since 2021-09-22 */ @Test public void testLoadingValidPrefixes() { - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + loadUnitsFile(database, "/test-unitsfile-valid2.txt"); assertEquals(7, database.getPrefix("A").getMultiplier()); assertEquals(11, database.getPrefix("B").getMultiplier()); assertEquals(13, database.getPrefix("C").getMultiplier()); + + // test invalid prefixes + assertThrows(NoSuchElementException.class, + () -> database.getPrefix("N/A")); } - + /** * Tests loading a valid unitfile with some units and preloaded prefixes - * + * * @since 2021-09-22 */ @Test public void testLoadingValidUnits() { - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); database.addUnit("fj", J.times(5)); database.addUnit("ej", J.times(8)); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + loadUnitsFile(database, "/test-unitsfile-valid1.txt"); - + final Unit expected1 = ((LinearUnit) U).withPrefix(A).withPrefix(B) .withPrefix(C); - final Unit actual1 = database.getUnit("test1"); + final var actual1 = database.getUnit("test1"); assertEquals(expected1, actual1); - + final Unit expected2 = ((LinearUnit) W).withPrefix(B) .times(((LinearUnit) V).withPrefix(C)); - final Unit actual2 = database.getUnit("test2"); + final var actual2 = database.getUnit("test2"); assertEquals(expected2, actual2); - + final Unit expected3 = ((LinearUnit) U) .times(A.getMultiplier() + C.getMultiplier() - B.getMultiplier()); - final Unit actual3 = database.getUnit("test3"); + final var actual3 = database.getUnit("test3"); assertEquals(expected3, actual3); - - final UnitValue expected4 = UnitValue.of(U, 1); - final UnitValue actual4 = database + + final var expected4 = UnitValue.of(U, 1); + final var actual4 = database .evaluateUnitExpression("-5 * U + -3 * U + 12 * U - 3 * U") .asUnitValue(); assertEquals(expected4, actual4); - + assertTrue(System.err.toString().length() > 0); } - + /** * Tests the iterator of the prefixless unit map. These tests are simple, as * the unit map iterator is simple. - * + * * @since 2021-10-07 */ @Test public void testPrefixedUnitMapIterator() { - final UnitDatabase database1 = new UnitDatabase(); - + final var database1 = new UnitDatabase(); + database1.addUnit("U", U); database1.addUnit("V", V); database1.addUnit("W", W); - - final Map map1 = database1.unitMap(); - final Iterator keyIterator1 = map1.keySet().iterator(); - final Iterator> entryIterator1 = map1.entrySet() + + final var map1 = database1.unitMap(); + final var keyIterator1 = map1.keySet().iterator(); + final var entryIterator1 = map1.entrySet() .iterator(); - + final Set expectedKeys = Set.of("U", "V", "W"); final Set actualKeys = new HashSet<>(); while (keyIterator1.hasNext()) { @@ -414,7 +457,7 @@ class UnitDatabaseTest { } assertEquals(expectedKeys, actualKeys); assertEquals(expectedKeys, map1.keySet()); - + final Set> expectedEntries = Set.of(entry("U", U), entry("V", V), entry("W", W)); final Set> actualEntries = new HashSet<>(); @@ -424,88 +467,88 @@ class UnitDatabaseTest { assertEquals(expectedEntries, actualEntries); assertEquals(expectedEntries, map1.entrySet()); } - + /** * Test that prefixes correctly apply to units. - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testPrefixes() { - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + // test the getPrefixesFromName method final List expected = Arrays.asList(C, B, A); assertEquals(expected, database.getPrefixesFromName("ABCU")); - + // get the product - final Unit abcuNonlinear = database.getUnit("ABCU"); + final var abcuNonlinear = database.getUnit("ABCU"); assert abcuNonlinear instanceof LinearUnit; - - final LinearUnit abcu = (LinearUnit) abcuNonlinear; + + final var abcu = (LinearUnit) abcuNonlinear; assertEquals(A.getMultiplier() * B.getMultiplier() * C.getMultiplier(), abcu.getConversionFactor(), 1e-15); } - + /** * Tests the functionnalites of the prefixless unit map. - * + * *

    * The map should be an auto-updating view of the units in the database. *

    - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testPrefixlessUnitMap() { - final UnitDatabase database = new UnitDatabase(); - final Map prefixlessUnits = database + final var database = new UnitDatabase(); + final var prefixlessUnits = database .unitMapPrefixless(true); - + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); - + // this should work because the map should be an auto-updating view assertTrue(prefixlessUnits.containsKey("U")); assertFalse(prefixlessUnits.containsKey("Z")); - + assertTrue(prefixlessUnits.containsValue(U)); assertFalse(prefixlessUnits.containsValue(NONLINEAR)); } - + /** * Tests that the database correctly stores and retrieves units, ignoring * prefixes. - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testPrefixlessUnits() { - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); - + assertTrue(database.containsUnitName("U")); assertFalse(database.containsUnitName("Z")); - + assertEquals(U, database.getUnit("U")); assertThrows(NoSuchElementException.class, () -> database.getUnit("Z")); } - + @Test public void testRemovableDuplicates() { final Map unitMap = new HashMap<>(); @@ -513,7 +556,7 @@ class UnitDatabaseTest { unitMap.put("metre", Metric.METRE); unitMap.put("m", Metric.METRE); unitMap.put("second", Metric.SECOND); - + assertTrue(UnitDatabase.isRemovableDuplicate(unitMap, entry("m", Metric.METRE))); assertTrue(UnitDatabase.isRemovableDuplicate(unitMap, @@ -523,131 +566,131 @@ class UnitDatabaseTest { assertFalse(UnitDatabase.isRemovableDuplicate(unitMap, entry("second", Metric.SECOND))); } - + @Test public void testToString() { - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("J", J); database.addUnit("K", J); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + if ("Unit Database with 1 units, 3 unit prefixes and 0 dimensions" .equals(database.toString())) { fail("Database counts by number of units, not number of unit names."); } - + assertEquals( "Unit Database with 2 units, 3 unit prefixes and 0 dimensions", database.toString()); } - + /** * Test that unit expressions return the correct value. - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testUnitExpressions() { // load units - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("U", U); database.addUnit("V", V); database.addUnit("W", W); database.addUnit("fj", J.times(5)); database.addUnit("ej", J.times(8)); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - + // first test - test prefixes and operations final Unit expected1 = J.withPrefix(A).withPrefix(B).withPrefix(C) .withPrefix(C); - final Unit actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W"); - + final var actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W"); + assertEquals(expected1, actual1); - + // second test - test addition and subtraction final Unit expected2 = J.times(58); - final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej"); - + final var actual2 = database.getUnitFromExpression("2 fj + 6 ej"); + assertEquals(expected2, actual2); - + // test incorrect expressions assertThrows(IllegalArgumentException.class, () -> database.getUnitFromExpression("U + V")); assertThrows(IllegalArgumentException.class, () -> database.getUnitFromExpression("U - V")); } - + /** * Tests both the unit name iterator and the name-unit entry iterator - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testUnitIterator() { // load units - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("J", J); database.addUnit("K", K); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); - - final int NUM_UNITS = database.unitMapPrefixless(true).size(); - final int NUM_PREFIXES = database.prefixMap(true).size(); - - final Iterator nameIterator = database.unitMap().keySet() + + final var NUM_UNITS = database.unitMapPrefixless(true).size(); + final var NUM_PREFIXES = database.prefixMap(true).size(); + + final var nameIterator = database.unitMap().keySet() .iterator(); - final Iterator> entryIterator = database.unitMap() + final var entryIterator = database.unitMap() .entrySet().iterator(); - - int expectedLength = 1; - int unitsWithThisLengthSoFar = 0; - + + var expectedLength = 1; + var unitsWithThisLengthSoFar = 0; + // loop 1000 times - for (int i = 0; i < 1000; i++) { + for (var i = 0; i < 1000; i++) { // expected length of next if (unitsWithThisLengthSoFar >= NUM_UNITS * (int) Math.pow(NUM_PREFIXES, expectedLength - 1)) { expectedLength++; unitsWithThisLengthSoFar = 0; } - + // test that stuff is valid - final String nextName = nameIterator.next(); - final Unit nextUnit = database.getUnit(nextName); - final Entry nextEntry = entryIterator.next(); - + final var nextName = nameIterator.next(); + final var nextUnit = database.getUnit(nextName); + final var nextEntry = entryIterator.next(); + assertEquals(expectedLength, nextName.length()); assertEquals(nextName, nextEntry.getKey()); assertEquals(nextUnit, nextEntry.getValue()); - + unitsWithThisLengthSoFar++; } - + // test toString for consistency - final String entryIteratorString = entryIterator.toString(); - for (int i = 0; i < 3; i++) { + final var entryIteratorString = entryIterator.toString(); + for (var i = 0; i < 3; i++) { assertEquals(entryIteratorString, entryIterator.toString()); } - - final String nameIteratorString = nameIterator.toString(); - for (int i = 0; i < 3; i++) { + + final var nameIteratorString = nameIterator.toString(); + for (var i = 0; i < 3; i++) { assertEquals(nameIteratorString, nameIterator.toString()); } } - + /** * Determine, given a unit name that could mean multiple things, which * meaning is chosen. @@ -655,36 +698,87 @@ class UnitDatabaseTest { * For example, "ABCU" could mean "A-B-C-U", "AB-C-U", or "A-BC-U". In this * case, "AB-C-U" is the correct choice. *

    - * + * * @since 2019-04-14 * @since v0.2.0 */ @Test public void testUnitPrefixCombinations() { // load units - final UnitDatabase database = new UnitDatabase(); - + final var database = new UnitDatabase(); + database.addUnit("J", J); - + database.addPrefix("A", A); database.addPrefix("B", B); database.addPrefix("C", C); database.addPrefix("AB", AB); database.addPrefix("BC", BC); - + // test 1 - AB-C-J vs A-BC-J vs A-B-C-J final Unit expected1 = J.withPrefix(AB).withPrefix(C); - final Unit actual1 = database.getUnit("ABCJ"); - + final var actual1 = database.getUnit("ABCJ"); + assertEquals(expected1, actual1); - + // test 2 - ABC-J vs AB-CJ vs AB-C-J database.addUnit("CJ", J.times(13)); database.addPrefix("ABC", UnitPrefix.valueOf(17)); - + final Unit expected2 = J.times(17); - final Unit actual2 = database.getUnit("ABCJ"); - + final var actual2 = database.getUnit("ABCJ"); + assertEquals(expected2, actual2); } + + /** + * Tests the ability to create, read, and delete unit sets. + * + * @since 2025-04-30 + */ + @Test + void testUnitSetsInvalid() { + final List units = List.of(Metric.SECOND, + BritishImperial.Length.INCH); + + final var database = new UnitDatabase(); + + assertThrows(IllegalArgumentException.class, + () -> database.addUnitSet("badtest", units)); + assertThrows(NoSuchElementException.class, + () -> database.getUnitSet("badtest")); + + database.addUnit("ft", BritishImperial.Length.FOOT); + database.addUnit("s", Metric.SECOND); + database.addUnit("dC", Metric.CELSIUS); + database.addUnit("K", Metric.KELVIN); + + assertThrows(IllegalArgumentException.class, + () -> database.getUnitSetFromExpression("K; dC"), + "getUnitSetFromExpression allowed nonlinear unit."); + assertThrows(IllegalArgumentException.class, + () -> database.getUnitSetFromExpression("ft; s"), + "getUnitSetFromExpression allowed units of different dimension."); + } + + /** + * Tests the ability to create, read, and delete unit sets. + * + * @since 2025-04-30 + */ + @Test + void testUnitSetsValid() { + final List units = List.of(BritishImperial.Length.FOOT, + BritishImperial.Length.INCH); + + final var database = new UnitDatabase(); + + database.addUnitSet("ftintest", units); + assertEquals(units, database.getUnitSet("ftintest")); + + database.addUnit("ft", BritishImperial.Length.FOOT); + database.addUnit("in", BritishImperial.Length.INCH); + assertEquals(units, database.getUnitSetFromExpression("ft; in")); + } + } -- cgit v1.2.3 From 06bd0e63f1b7bc83009ec3f4d7b048dea015529f Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 9 May 2025 21:20:00 -0500 Subject: Add tests for Presenter --- src/main/resources/unitsfile.txt | 4 +- src/test/java/sevenUnitsGUI/PresenterTest.java | 119 ++++++++++++------------- 2 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/main/resources/unitsfile.txt b/src/main/resources/unitsfile.txt index dc33abd..17fe98a 100644 --- a/src/main/resources/unitsfile.txt +++ b/src/main/resources/unitsfile.txt @@ -194,8 +194,8 @@ ch chain furlong 10 chain mile 1760 yard mi mile -ftin foot; inch -ydftin yard; foot; inch +ftin ft; in +ydftin yd; ft; in # Imperial area units acre chain * furlong diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 8b16365..1d9b45b 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -21,10 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import java.math.RoundingMode; import java.nio.file.Path; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -33,12 +31,12 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.BritishImperial; import sevenUnits.unit.LinearUnit; -import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; @@ -54,7 +52,7 @@ import sevenUnits.utils.UncertainDouble; * Note: this test outputs a lot to the standard output, because creating a * {@link UnitDatabase} and converting with a {@link ViewBot} both trigger * println statements. - * + * * @author Adrien Hopkins * * @since v0.4.0 @@ -78,7 +76,7 @@ public final class PresenterTest { * @since v0.4.0 * @since 2022-04-16 */ - private static final Stream> getRoundingRules() { + private static Stream> getRoundingRules() { final var SCIENTIFIC_ROUNDING = StandardDisplayRules.uncertaintyBased(); final var INTEGER_ROUNDING = StandardDisplayRules.fixedDecimals(0); final var SIG_FIG_ROUNDING = StandardDisplayRules.fixedPrecision(4); @@ -86,79 +84,80 @@ public final class PresenterTest { return Stream.of(SCIENTIFIC_ROUNDING, INTEGER_ROUNDING, SIG_FIG_ROUNDING); } - private static final Stream, Map>> getSearchRules() { + private static Stream, Map>> getSearchRules() { return SEARCH_RULES; } - private static final Set names(Set units) { + private static Set names(Set units) { return units.stream().map(Nameable::getName).collect(Collectors.toSet()); } + private static Stream testConvertExpressions() { + return Stream.of( + Arguments.of("10000.0 m", "km", "10000.0 m = 10.00000 km"), + Arguments.of("1.0 m", "ft; in", "1.0 m = 3 ft + 3 in"), + Arguments.of("1.0 m", "ftin", "1.0 m = 3 ft + 3 in")); + } + + private static Stream testConvertUnits() { + return Stream.of( + Arguments.of("m", "km", 10000.0, "10000.0 m = 10.00000 km"), + Arguments.of("m", "ftin", 1.0, "1.0 m = 3 ft + 3 in")); + } + /** * Test method for {@link Presenter#convertExpressions} - * + * * @since v0.4.0 * @since 2022-02-12 */ - @Test - void testConvertExpressions() { + @ParameterizedTest + @MethodSource + void testConvertExpressions(String from, String to, String expectedOutput) { // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); - viewBot.setFromExpression("10000.0 m"); - viewBot.setToExpression("km"); + viewBot.setFromExpression(from); + viewBot.setToExpression(to); // convert expression presenter.convertExpressions(); // test result - final List outputs = viewBot - .expressionConversionList(); - assertEquals("10000.0 m = 10.00000 km", - outputs.get(outputs.size() - 1).toString()); + final var outputs = viewBot.expressionConversionList(); + assertEquals(expectedOutput, outputs.get(outputs.size() - 1).toString()); } /** - * Tests that unit-conversion Views can correctly convert units - * + * Test method for {@link Presenter#convertUnits} + * * @since v0.4.0 * @since 2022-02-12 */ - @Test - void testConvertUnits() { + @ParameterizedTest + @MethodSource + void testConvertUnits(String from, String to, double value, + String expectedOutput) { // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); - viewBot.setFromUnitNames(names(testUnits)); - viewBot.setToUnitNames(names(testUnits)); - viewBot.setFromSelection("metre"); - viewBot.setToSelection("kilometre"); - viewBot.setInputValue("10000.0"); + viewBot.setFromSelection(from); + viewBot.setToSelection(to); + viewBot.setInputValue(Double.toString(value)); - // convert units + // convert expression presenter.convertUnits(); - /* - * use result from system as expected - I'm not testing unit conversion - * here (that's for the backend tests), I'm just testing that it correctly - * calls the unit conversion system - */ - final LinearUnitValue expectedInput = LinearUnitValue.of(Metric.METRE, - UncertainDouble.fromRoundedString("10000.0")); - final LinearUnitValue expectedOutput = expectedInput - .convertTo(Metric.KILOMETRE); - final UnitConversionRecord expectedUC = UnitConversionRecord.valueOf( - expectedInput.getUnit().getName(), - expectedOutput.getUnit().getName(), "10000.0", - expectedOutput.getValue().toString(false, RoundingMode.HALF_EVEN)); - assertEquals(List.of(expectedUC), viewBot.unitConversionList()); + // test result + final var outputs = viewBot.unitConversionList(); + assertEquals(expectedOutput, outputs.get(outputs.size() - 1).toString()); } - + /** * Ensures that the default unitfile can be disabled. - * + * * @since v1.0.0 * @since 2025-02-23 */ @@ -175,7 +174,7 @@ public final class PresenterTest { /** * Tests that duplicate units are successfully removed, if that is asked for - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -208,7 +207,7 @@ public final class PresenterTest { /** * Tests that one-way conversion correctly filters From and To units - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -242,7 +241,7 @@ public final class PresenterTest { /** * Tests the prefix-viewing functionality. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -272,7 +271,7 @@ public final class PresenterTest { /** * Tests that rounding rules are used correctly. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -291,9 +290,9 @@ public final class PresenterTest { presenter.convertUnits(); // test the result of the rounding - final String expectedOutputString = roundingRule + final var expectedOutputString = roundingRule .apply(UncertainDouble.fromRoundedString("12.3456789")); - final String actualOutputString = viewBot.unitConversionList().get(0) + final var actualOutputString = viewBot.unitConversionList().get(0) .outputValueString(); assertEquals(expectedOutputString, actualOutputString); } @@ -327,7 +326,7 @@ public final class PresenterTest { .apply(Map.entry("inch", BritishImperial.Length.INCH)).keySet()); expectedOutput.addAll( searchRule.apply(Map.entry("metre", Metric.METRE)).keySet()); - final Set actualOutput = viewBot.getFromUnitNames(); + final var actualOutput = viewBot.getFromUnitNames(); // test output assertEquals(expectedOutput, actualOutput); @@ -335,7 +334,7 @@ public final class PresenterTest { /** * Tests that settings can be saved to and loaded from a file. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -369,7 +368,7 @@ public final class PresenterTest { /** * Ensures the Presenter generates the correct data upon a unit-viewing. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -403,15 +402,15 @@ public final class PresenterTest { /** * Test for {@link Presenter#updateView()} - * + * * @since v0.4.0 * @since 2022-02-12 */ @Test void testUpdateView() { // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); presenter.setOneWayConversionEnabled(false); presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES); @@ -433,8 +432,8 @@ public final class PresenterTest { // filter to length units only, then get the filtered sets of units presenter.updateView(); - final Set fromUnits = viewBot.getFromUnitNames(); - final Set toUnits = viewBot.getToUnitNames(); + final var fromUnits = viewBot.getFromUnitNames(); + final var toUnits = viewBot.getToUnitNames(); // test that fromUnits/toUnits is [METRE, KILOMETRE] assertEquals(Set.of("metre", "kilometre"), fromUnits); -- cgit v1.2.3 From fc5045c4da18b6fd811c6748aa7b5184a80f6769 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 17 May 2025 23:03:29 -0500 Subject: Setup automated javadoc ./gradlew javadoc --- build.gradle | 3 +++ src/main/java/sevenUnits/unit/LinearUnitValue.java | 1 - src/main/java/sevenUnits/unit/MultiUnit.java | 4 ++-- src/main/java/sevenUnits/unit/UnitDatabase.java | 6 +++--- .../java/sevenUnits/utils/ConditionalExistenceCollections.java | 1 - src/main/java/sevenUnits/utils/DecimalComparison.java | 7 ++++--- src/main/java/sevenUnits/utils/ObjectProduct.java | 2 +- src/main/java/sevenUnits/utils/SemanticVersionNumber.java | 2 +- src/main/java/sevenUnits/utils/UncertainDouble.java | 6 +++--- src/main/java/sevenUnitsGUI/Presenter.java | 2 +- src/main/java/sevenUnitsGUI/UnitConversionRecord.java | 8 ++++---- src/main/java/sevenUnitsGUI/UnitConversionView.java | 3 +-- src/main/java/sevenUnitsGUI/ViewBot.java | 2 +- 13 files changed, 24 insertions(+), 23 deletions(-) diff --git a/build.gradle b/build.gradle index b775b70..929f1c2 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,9 @@ jacocoTestReport { javadoc { destinationDir = file("${buildDir}/docs/javadoc") + // https://gist.github.com/claudioaltamura/aba1f6506a53b9f5499fd507abd572df + (options as StandardJavadocDocletOptions) + .tags("apiNote:a:API Note:", "implSpec:a:Implementation Requirements:", "implNote:a:Implementation Note:") } run { diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index e239b49..678c59c 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -58,7 +58,6 @@ public final class LinearUnitValue { * * @param unit unit to express with * @param value value to express - * @param uncertainty absolute uncertainty of value * @return uncertain {@code LinearUnitValue} instance * @since 2020-07-26 */ diff --git a/src/main/java/sevenUnits/unit/MultiUnit.java b/src/main/java/sevenUnits/unit/MultiUnit.java index 950c547..c6d3b97 100644 --- a/src/main/java/sevenUnits/unit/MultiUnit.java +++ b/src/main/java/sevenUnits/unit/MultiUnit.java @@ -101,7 +101,7 @@ public final class MultiUnit extends Unitlike> { * output of this method. * * @param other unit to convert to - * @param value value to convert + * @param values values to convert * @return converted value * @since 2020-10-03 * @throws IllegalArgumentException if {@code other} is incompatible for @@ -129,7 +129,7 @@ public final class MultiUnit extends Unitlike> { * output of this method. * * @param other unit to convert to - * @param value value to convert + * @param values values to convert * @return converted value * @since 2020-10-03 * @throws IllegalArgumentException if {@code other} is incompatible for diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 690430b..444b366 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1947,7 +1947,7 @@ public final class UnitDatabase { * * @param file file to read * @throws NullPointerException if file is null - * @returns list of errors that happened when loading file + * @return list of errors that happened when loading file * @since 2019-01-13 * @since v0.1.0 */ @@ -2017,7 +2017,7 @@ public final class UnitDatabase { * * @param file file to read * @throws NullPointerException if file is null - * @returns list of errors that happened when loading file + * @return list of errors that happened when loading file * @since 2019-01-13 * @since v0.1.0 */ @@ -2113,11 +2113,11 @@ public final class UnitDatabase { *

    * Specifically, the operations that will throw an IllegalStateException if * the map is infinite in size are: + *

    *
      *
    • {@code unitMap.entrySet().toArray()} (either overloading)
    • *
    • {@code unitMap.keySet().toArray()} (either overloading)
    • *
    - *

    *

    * Because of ambiguities between prefixes (i.e. kilokilo = mega), the map's * {@link PrefixedUnitMap#containsValue containsValue} and diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index b71a4e0..cb3b8ce 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -54,7 +54,6 @@ import java.util.function.Predicate; * @author Adrien Hopkins * @since 2019-10-17 */ -// TODO add conditional existence Lists and Sorted/Navigable Sets/Maps public final class ConditionalExistenceCollections { /** * Elements in this collection only exist if they meet a condition. diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index 0515b6b..62c3720 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -69,7 +69,7 @@ public final class DecimalComparison { * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 - * @see #hashCode(double) + * @see #hash(double) */ public static final boolean equals(final double a, final double b) { return DecimalComparison.equals(a, b, DOUBLE_EPSILON); @@ -194,7 +194,7 @@ public final class DecimalComparison { * @param b second value to test * @return whether they are equal * @since 2020-09-07 - * @see #hashCode(double) + * @see #hash(double) */ public static final boolean equals(final UncertainDouble a, final UncertainDouble b) { @@ -238,12 +238,13 @@ public final class DecimalComparison { /** * Takes the hash code of doubles. Values that are equal according to - * {@link #equals(double, double)} will have the same hash code. + * {@link #equals(double, double)} will probably have the same hash code. * * @param d double to hash * @return hash code of double * @since 2019-10-16 */ + // TODO reconsider using this public static final int hash(final double d) { return Float.hashCode((float) d); } diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 4ed70be..d403bdc 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -265,7 +265,7 @@ public class ObjectProduct implements Nameable { * Returns this product to an exponent, where every dimension is rounded to * the nearest integer. * - * This function will send a warning (via {@link System.err}) if the rounding + * This function will send a warning (via standard error) if the rounding * significantly changes the value. * * @since 2024-08-22 diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index fc47baa..937d474 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -367,7 +367,7 @@ public final class SemanticVersionNumber * @return {@code SemanticVersionNumber} instance * @since v0.4.0 * @since 2022-02-19 - * @see {@link #toString} + * @see #toString */ public static final SemanticVersionNumber fromString(String versionString) { // parse & validate version string diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index 1f7ab79..ca94817 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -61,7 +61,7 @@ public final class UncertainDouble implements Comparable { } /** - * Parses a string in the form of {@link UncertainDouble#toString(boolean)} + * Parses a string in the form of {@link UncertainDouble#toString(boolean, RoundingMode)} * and returns the corresponding {@code UncertainDouble} instance. *

    * This method allows some alternative forms of the string representation, @@ -346,8 +346,8 @@ public final class UncertainDouble implements Comparable { /** * Returns a string representation of this {@code UncertainDouble}. *

    - * This method returns the same value as {@link #toString(boolean)}, but - * {@code showUncertainty} is true if and only if the uncertainty is + * This method returns the same value as {@link #toString(boolean, RoundingMode)}, + * but {@code showUncertainty} is true if and only if the uncertainty is * non-zero. * *

    diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 0dc7ea0..e383b2c 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -1205,7 +1205,7 @@ public final class Presenter { * @param oneWayConversionEnabled whether not one-way conversion should be * enabled * @since 2022-03-30 - * @see {@link #isOneWayConversionEnabled} + * @see #oneWayConversionEnabled */ public void setOneWayConversionEnabled(boolean oneWayConversionEnabled) { this.oneWayConversionEnabled = oneWayConversionEnabled; diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java index fa64ee9..da1a9d2 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java @@ -31,8 +31,8 @@ public final class UnitConversionRecord { /** * Gets a {@code UnitConversionRecord} from two linear unit values * - * @param input input unit & value - * @param output output unit & value + * @param input input unit & value + * @param output output unit & value * @return unit conversion record * @since v0.4.0 * @since 2022-04-09 @@ -48,8 +48,8 @@ public final class UnitConversionRecord { /** * Gets a {@code UnitConversionRecord} from two unit values * - * @param input input unit & value - * @param output output unit & value + * @param input input unit & value + * @param output output unit & value * @return unit conversion record * @since v0.4.0 * @since 2022-04-09 diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index b9077f7..e3fb28f 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -111,8 +111,7 @@ public interface UnitConversionView extends View { /** * Shows the output of a unit conversion. * - * @param input input unit & value (obtained from this view) - * @param output output unit & value + * @param uc record of unit conversion * @since v0.4.0 * @since 2021-12-24 */ diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index 8fff46d..aea30bb 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -387,7 +387,7 @@ public final class ViewBot } /** - * @param selectedDimension the selectedDimension to set + * @param selectedDimensionName the selectedDimensionName to set * @since 2022-01-29 */ public void setSelectedDimensionName( -- cgit v1.2.3 From 779bf983226f9799dc19d84a3f2cb9a3e26cd540 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 21 May 2025 17:59:40 -0500 Subject: Add tests for submethods of ExpressionParser These tests fail right now, because ExpressionParser has a bug. They should help me fix it. --- .../sevenUnits/utils/ExpressionParserTest.java | 77 +++++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java index 3a95285..ede931f 100644 --- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java +++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java @@ -17,6 +17,7 @@ package sevenUnits.utils; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; import java.util.stream.IntStream; @@ -39,9 +40,11 @@ class ExpressionParserTest { private static final ExpressionParser numberParser = new ExpressionParser.Builder<>( Integer::parseInt).addBinaryOperator("+", (o1, o2) -> o1 + o2, 0) .addBinaryOperator("-", (o1, o2) -> o1 - o2, 0) - .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1) - .addBinaryOperator("/", (o1, o2) -> o1 / o2, 1) - .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build(); + .addUnaryOperator("neg", o1 -> -o1, 1) + .addBinaryOperator("*", (o1, o2) -> o1 * o2, 2) + .addBinaryOperator("/", (o1, o2) -> o1 / o2, 2) + .addUnaryOperator("recip", o1 -> 1 / o1, 3) + .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 4).build(); /** * The expressions used in the expression parsing tests @@ -69,6 +72,45 @@ class ExpressionParserTest { .mapToObj(i -> Arguments.of(TEST_EXPRESSIONS.get(i), RESULTS[i])); } + private static final Stream testConvertExpressionToRPN() { + return Stream.of(Arguments.of("1 + 2 ^ 5 * 3", "1 2 5 ^ 3 * +"), + Arguments.of("(1 + 2) ^ 5 * 3", "1 2 + 5 ^ 3 *"), + Arguments.of("12 * 5 + (3 ^ (2 * 3) - 72) / (3 + 3 * 2)", + "12 5 * 3 2 3 * ^ 72 - 3 3 2 * + / +"), + Arguments.of("1 + 2 + 3 + 4", "1 2 + 3 + 4 +"), + Arguments.of("12 - 4 - 3", "12 4 - 3 -"), + Arguments.of("12 - (4 - 3)", "12 4 3 - -"), + Arguments.of("1 / 2 + 3", "1 2 / 3 +"), Arguments.of("12", "12"), + Arguments.of("2 * 3 + 4", "2 3 * 4 +"), + Arguments.of("(2 * 3) + 4", "2 3 * 4 +"), + Arguments.of("2 * 3 - 4", "2 3 * 4 -"), + Arguments.of("(2 * 3) - 4", "2 3 * 4 -"), + Arguments.of("2 * (3 + 4)", "2 3 4 + *"), + Arguments.of("2 * (3 - 4)", "2 3 4 - *"), + Arguments.of("neg 2", "2 neg"), + Arguments.of("1 + neg 2", "1 2 neg +")); + } + + private static final Stream testParseRPN() { + return Stream.of(Arguments.of("1 2 5 ^ 3 * +", 97), + Arguments.of("1 2 + 5 ^ 3 *", 729), + Arguments.of("12 5 * 3 2 3 * ^ 72 - 3 3 2 * + / +", 133), + Arguments.of("1 2 + 3 + 4 +", 10), Arguments.of("12 4 - 3 -", 5), + Arguments.of("12 4 3 - -", 11), Arguments.of("1 2 / 3 +", 3), + Arguments.of("12", 12), Arguments.of("2 3 * 4 +", 10), + Arguments.of("2 3 * 4 -", 2), Arguments.of("2 3 4 + *", 14), + Arguments.of("2 3 4 - *", -2), Arguments.of("2 neg", -2), + Arguments.of("1 2 neg +", -1)); + } + + private static final Stream testInvalidExpression() { + return Stream.of("+", "1 +", "1 + * 2", "1 (+ 1)", "neg"); + } + + private static final Stream testInvalidRPN() { + return Stream.of("+", "1 +", "1 + * 2", "1 * 2", "1 2", "neg"); + } + /** * Test method for * {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}. @@ -78,4 +120,33 @@ class ExpressionParserTest { public void testParseExpression(String expression, int value) { assertEquals(value, numberParser.parseExpression(expression)); } + + @ParameterizedTest + @MethodSource + public void testConvertExpressionToRPN(String expression, + String expectedRPN) { + assertEquals(expectedRPN, + numberParser.convertExpressionToReversePolish(expression)); + } + + @ParameterizedTest + @MethodSource + public void testInvalidExpression(String expression) { + assertThrows(RuntimeException.class, + () -> numberParser.convertExpressionToReversePolish(expression)); + } + + @ParameterizedTest + @MethodSource + public void testParseRPN(String expressionRPN, int value) { + assertEquals(value, + numberParser.parseReversePolishExpression(expressionRPN)); + } + + @ParameterizedTest + @MethodSource + public void testInvalidRPN(String expressionRPN) { + assertThrows(RuntimeException.class, + () -> numberParser.parseReversePolishExpression(expressionRPN)); + } } -- cgit v1.2.3 From 4cb7e4eb4a9213304ee5b83c9aaa0427e9a8fe31 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 21 May 2025 18:23:35 -0500 Subject: ExpressionParser: use correct operand order Most of the internal problems with the expression parser happened because I was accepting the arguments for binary operators in the wrong order. For example, '2 - 1' became '1 2 -', not '2 1 -'. The likely cause of this error is the following sequence of events: - In commit 6dbd32cd, I created the code for interpreting RPN. I accepted two arguments from the stack (o1 and o2), then performed o1 o2. However, because stacks are in LIFO order, I should have actually done o2 o1. - Later, in commit 94349688, I created the code for converting an infix expression to RPN. Creating the expressions in the correct order did not work, because my interpreter used the incorrect order. To 'fix' this problem, I created the expressions in the incorrect order. I did not notice any discrepancy, probably because I was not testing the individual methods, only the two-step whole (which found no errors). --- src/main/java/sevenUnits/utils/ExpressionParser.java | 4 ++-- src/test/java/sevenUnits/utils/ExpressionParserTest.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index e248ff0..2bc8952 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -675,7 +675,7 @@ public final class ExpressionParser { final String operand2 = components .remove(highestPriorityOperatorPosition - 1); components.add(highestPriorityOperatorPosition - 1, - operand2 + " " + operand1 + " " + binaryOperator); + operand1 + " " + operand2 + " " + binaryOperator); break; default: throw new AssertionError("Expected operator, found non-operator."); @@ -826,7 +826,7 @@ public final class ExpressionParser { final BinaryOperator binaryOperator = this.binaryOperators .get(item); - stack.push(binaryOperator.apply(o1, o2)); + stack.push(binaryOperator.apply(o2, o1)); break; case NUMERIC_OPERATOR: diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java index ede931f..15701ce 100644 --- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java +++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java @@ -27,7 +27,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -// TODO add tests for expression-to-RPN and RPN-to-result /** * A test for the {@code ExpressionParser} class. This is NOT part of this * program's public API. -- cgit v1.2.3 From 8df414ced2018e685f034424c3d8c7813d18805c Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 21 May 2025 18:42:16 -0500 Subject: Throw error on expressions with too many operators --- src/main/java/sevenUnits/utils/ExpressionParser.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 2bc8952..4f9cfa8 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -684,15 +684,11 @@ public final class ExpressionParser { // 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); - } - while (expressionRPN.charAt(expressionRPN.length() - 1) == ' ') { - expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1); + if (components.size() != 1) { + throw new IllegalArgumentException( + "Invalid expression \"" + expression + "\"."); } - return expressionRPN; + return components.get(0).replaceAll(" +", " ").trim(); } /** -- cgit v1.2.3 From e86fb71e1665cb9e8511bafd54df0fb8cbca5adc Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 30 May 2025 19:48:42 -0500 Subject: Remove Unitlike/MultiUnit I ended up never using this code - it was simpler to just use lists of units and values. Making a whole new object for lists of units, and an abstract class for things that convert things other than doubles, is needlessly complicated, and doesn't solve any major issues. For example, I still need to store each Unitlike type in a different collection, because it will have a different type. --- .../java/sevenUnits/unit/FunctionalUnitlike.java | 73 ------ src/main/java/sevenUnits/unit/LinearUnit.java | 9 - src/main/java/sevenUnits/unit/MultiUnit.java | 161 ------------- src/main/java/sevenUnits/unit/Unit.java | 52 ---- src/main/java/sevenUnits/unit/UnitValue.java | 21 -- src/main/java/sevenUnits/unit/Unitlike.java | 262 --------------------- src/main/java/sevenUnits/unit/UnitlikeValue.java | 176 -------------- src/test/java/sevenUnits/unit/MultiUnitTest.java | 110 --------- 8 files changed, 864 deletions(-) delete mode 100644 src/main/java/sevenUnits/unit/FunctionalUnitlike.java delete mode 100644 src/main/java/sevenUnits/unit/MultiUnit.java delete mode 100644 src/main/java/sevenUnits/unit/Unitlike.java delete mode 100644 src/main/java/sevenUnits/unit/UnitlikeValue.java delete mode 100644 src/test/java/sevenUnits/unit/MultiUnitTest.java diff --git a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java deleted file mode 100644 index e9b4d1f..0000000 --- a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (C) 2020 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package sevenUnits.unit; - -import java.util.function.DoubleFunction; -import java.util.function.ToDoubleFunction; - -import sevenUnits.utils.NameSymbol; -import sevenUnits.utils.ObjectProduct; - -/** - * A unitlike form that converts using two conversion functions. - * - * @since 2020-09-07 - */ -final class FunctionalUnitlike extends Unitlike { - /** - * A function that accepts a value in the unitlike form's base and returns a - * value in the unitlike form. - * - * @since 2020-09-07 - */ - private final DoubleFunction converterFrom; - - /** - * A function that accepts a value in the unitlike form and returns a value - * in the unitlike form's base. - */ - private final ToDoubleFunction converterTo; - - /** - * Creates the {@code FunctionalUnitlike}. - * - * @param base unitlike form's base - * @param converterFrom function that accepts a value in the unitlike form's - * base and returns a value in the unitlike form. - * @param converterTo function that accepts a value in the unitlike form - * and returns a value in the unitlike form's base. - * @throws NullPointerException if any argument is null - * @since 2019-05-22 - */ - protected FunctionalUnitlike(ObjectProduct unitBase, NameSymbol ns, - DoubleFunction converterFrom, ToDoubleFunction converterTo) { - super(unitBase, ns); - this.converterFrom = converterFrom; - this.converterTo = converterTo; - } - - @Override - protected V convertFromBase(double value) { - return this.converterFrom.apply(value); - } - - @Override - protected double convertToBase(V value) { - return this.converterTo.applyAsDouble(value); - } - -} diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index a230f28..3c3703c 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -74,15 +74,6 @@ public final class LinearUnit extends Unit { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - /** - * @return the base unit associated with {@code unitlike}, as a - * {@code LinearUnit}. - * @since 2020-10-02 - */ - public static LinearUnit getBase(final Unitlike unit) { - return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); - } - /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and diff --git a/src/main/java/sevenUnits/unit/MultiUnit.java b/src/main/java/sevenUnits/unit/MultiUnit.java deleted file mode 100644 index c6d3b97..0000000 --- a/src/main/java/sevenUnits/unit/MultiUnit.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright (C) 2020 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package sevenUnits.unit; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import sevenUnits.utils.NameSymbol; -import sevenUnits.utils.ObjectProduct; - -/** - * A combination of units, like "5 foot + 7 inch". All but the last units should - * have a whole number value associated with them. - * - * @since 2020-10-02 - */ -public final class MultiUnit extends Unitlike> { - /** - * Creates a {@code MultiUnit} from its units. It will not have a name or - * symbol. - * - * @since 2020-10-03 - */ - public static final MultiUnit of(LinearUnit... units) { - return of(Arrays.asList(units)); - } - - /** - * Creates a {@code MultiUnit} from its units. It will not have a name or - * symbol. - * - * @since 2020-10-03 - */ - public static final MultiUnit of(List units) { - if (units.size() < 1) - throw new IllegalArgumentException("Must have at least one unit"); - final ObjectProduct unitBase = units.get(0).getBase(); - for (final LinearUnit unit : units) { - if (!unitBase.equals(unit.getBase())) - throw new IllegalArgumentException( - "All units must have the same base."); - } - return new MultiUnit(new ArrayList<>(units), unitBase, NameSymbol.EMPTY); - } - - /** - * The units that make up this value. - */ - private final List units; - - /** - * Creates a {@code MultiUnit}. - * - * @since 2020-10-03 - */ - private MultiUnit(List units, ObjectProduct unitBase, - NameSymbol ns) { - super(unitBase, ns); - this.units = units; - } - - @Override - protected List convertFromBase(double value) { - final List values = new ArrayList<>(this.units.size()); - double temp = value; - - for (final LinearUnit unit : this.units.subList(0, - this.units.size() - 1)) { - values.add(Math.floor(temp / unit.getConversionFactor())); - temp %= unit.getConversionFactor(); - } - - values.add(this.units.size() - 1, - this.units.get(this.units.size() - 1).convertFromBase(temp)); - - return values; - } - - /** - * Converts a value expressed in this unitlike form to a value expressed in - * {@code other}. - * - * @implSpec If conversion is possible, this implementation returns - * {@code other.convertFromBase(this.convertToBase(value))}. - * Therefore, overriding either of those methods will change the - * output of this method. - * - * @param other unit to convert to - * @param values values to convert - * @return converted value - * @since 2020-10-03 - * @throws IllegalArgumentException if {@code other} is incompatible for - * conversion with this unitlike form (as - * tested by {@link Unit#canConvertTo}). - * @throws NullPointerException if other is null - */ - public final , V> V convertTo(U other, - double... values) { - final List valueList = new ArrayList<>(values.length); - for (final double d : values) { - valueList.add(d); - } - - return this.convertTo(other, valueList); - } - - /** - * Converts a value expressed in this unitlike form to a value expressed in - * {@code other}. - * - * @implSpec If conversion is possible, this implementation returns - * {@code other.convertFromBase(this.convertToBase(value))}. - * Therefore, overriding either of those methods will change the - * output of this method. - * - * @param other unit to convert to - * @param values values to convert - * @return converted value - * @since 2020-10-03 - * @throws IllegalArgumentException if {@code other} is incompatible for - * conversion with this unitlike form (as - * tested by {@link Unit#canConvertTo}). - * @throws NullPointerException if other is null - */ - public final double convertTo(Unit other, double... values) { - final List valueList = new ArrayList<>(values.length); - for (final double d : values) { - valueList.add(d); - } - - return this.convertTo(other, valueList); - } - - @Override - protected double convertToBase(List value) { - if (value.size() != this.units.size()) - throw new IllegalArgumentException("Wrong number of values for " - + this.units.size() + "-unit MultiUnit."); - - double baseValue = 0; - for (int i = 0; i < this.units.size(); i++) { - baseValue += value.get(i) * this.units.get(i).getConversionFactor(); - } - return baseValue; - } -} diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 59e928a..d25b362 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -136,15 +136,6 @@ public abstract class Unit implements Nameable { this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); } - /** - * @return this unit as a {@link Unitlike} - * @since 2020-09-07 - */ - public final Unitlike asUnitlike() { - return Unitlike.fromConversionFunctions(this.getBase(), - this::convertFromBase, this::convertToBase, this.getNameSymbol()); - } - /** * Checks if a value expressed in this unit can be converted to a value * expressed in {@code other} @@ -160,21 +151,6 @@ public abstract class Unit implements Nameable { return Objects.equals(this.getBase(), other.getBase()); } - /** - * Checks if a value expressed in this unit can be converted to a value - * expressed in {@code other} - * - * @param other unit or unitlike form to test with - * @return true if they are compatible - * @since 2019-01-13 - * @since v0.1.0 - * @throws NullPointerException if other is null - */ - public final boolean canConvertTo(final Unitlike other) { - Objects.requireNonNull(other, "other must not be null."); - return Objects.equals(this.getBase(), other.getBase()); - } - /** * Converts from a value expressed in this unit's base unit to a value * expressed in this unit. @@ -225,34 +201,6 @@ public abstract class Unit implements Nameable { String.format("Cannot convert from %s to %s.", this, other)); } - /** - * Converts a value expressed in this unit to a value expressed in - * {@code other}. - * - * @implSpec If conversion is possible, this implementation returns - * {@code other.convertFromBase(this.convertToBase(value))}. - * Therefore, overriding either of those methods will change the - * output of this method. - * - * @param other unitlike form to convert to - * @param value value to convert - * @param type of value to convert to - * @return converted value - * @since 2020-09-07 - * @throws IllegalArgumentException if {@code other} is incompatible for - * conversion with this unit (as tested by - * {@link Unit#canConvertTo}). - * @throws NullPointerException if other is null - */ - public final W convertTo(final Unitlike other, final double value) { - Objects.requireNonNull(other, "other must not be null."); - if (this.canConvertTo(other)) - return other.convertFromBase(this.convertToBase(value)); - else - throw new IllegalArgumentException( - String.format("Cannot convert from %s to %s.", this, other)); - } - /** * Converts from a value expressed in this unit to a value expressed in this * unit's base unit. diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index 2d01831..9b485e3 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -63,27 +63,6 @@ public final class UnitValue { return this.unit.canConvertTo(other); } - /** - * @return true if this value can be converted to {@code other}. - * @since 2020-10-01 - */ - public final boolean canConvertTo(Unitlike other) { - return this.unit.canConvertTo(other); - } - - /** - * Returns a UnitlikeValue that represents the same value expressed in a - * different unitlike form. - * - * @param other new unit to express value in - * @return value expressed in {@code other} - */ - public final , W> UnitlikeValue convertTo( - U other) { - return UnitlikeValue.of(other, - this.unit.convertTo(other, this.getValue())); - } - /** * Returns a UnitValue that represents the same value expressed in a * different unit diff --git a/src/main/java/sevenUnits/unit/Unitlike.java b/src/main/java/sevenUnits/unit/Unitlike.java deleted file mode 100644 index fef424e..0000000 --- a/src/main/java/sevenUnits/unit/Unitlike.java +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Copyright (C) 2020 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package sevenUnits.unit; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.DoubleFunction; -import java.util.function.ToDoubleFunction; - -import sevenUnits.utils.NameSymbol; -import sevenUnits.utils.Nameable; -import sevenUnits.utils.ObjectProduct; - -/** - * An object that can convert a value between multiple forms (instances of the - * object); like a unit but the "converted value" can be any type. - * - * @since 2020-09-07 - */ -public abstract class Unitlike implements Nameable { - /** - * Returns a unitlike form from its base and the functions it uses to convert - * to and from its base. - * - * @param base unitlike form's base - * @param converterFrom function that accepts a value expressed in the - * unitlike form's base and returns that value expressed - * in this unitlike form. - * @param converterTo function that accepts a value expressed in the - * unitlike form and returns that value expressed in the - * unit's base. - * @return a unitlike form that uses the provided functions to convert. - * @since 2020-09-07 - * @throws NullPointerException if any argument is null - */ - public static final Unitlike fromConversionFunctions( - final ObjectProduct base, - final DoubleFunction converterFrom, - final ToDoubleFunction converterTo) { - return new FunctionalUnitlike<>(base, NameSymbol.EMPTY, converterFrom, - converterTo); - } - - /** - * Returns a unitlike form from its base and the functions it uses to convert - * to and from its base. - * - * @param base unitlike form's base - * @param converterFrom function that accepts a value expressed in the - * unitlike form's base and returns that value expressed - * in this unitlike form. - * @param converterTo function that accepts a value expressed in the - * unitlike form and returns that value expressed in the - * unit's base. - * @param ns names and symbol of unit - * @return a unitlike form that uses the provided functions to convert. - * @since 2020-09-07 - * @throws NullPointerException if any argument is null - */ - public static final Unitlike fromConversionFunctions( - final ObjectProduct base, - final DoubleFunction converterFrom, - final ToDoubleFunction 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 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 dimension = null; - - /** - * @param unitBase - * @since 2020-09-07 - */ - protected Unitlike(ObjectProduct unitBase, NameSymbol ns) { - this.unitBase = Objects.requireNonNull(unitBase, - "unitBase may not be null"); - this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); - } - - /** - * Checks if a value expressed in this unitlike form can be converted to a - * value expressed in {@code other} - * - * @param other unit or unitlike form to test with - * @return true if they are compatible - * @since 2019-01-13 - * @since v0.1.0 - * @throws NullPointerException if other is null - */ - public final boolean canConvertTo(final Unit other) { - Objects.requireNonNull(other, "other must not be null."); - return Objects.equals(this.getBase(), other.getBase()); - } - - /** - * Checks if a value expressed in this unitlike form can be converted to a - * value expressed in {@code other} - * - * @param other unit or unitlike form to test with - * @return true if they are compatible - * @since 2019-01-13 - * @since v0.1.0 - * @throws NullPointerException if other is null - */ - public final boolean canConvertTo(final Unitlike other) { - Objects.requireNonNull(other, "other must not be null."); - return Objects.equals(this.getBase(), other.getBase()); - } - - protected abstract V convertFromBase(double value); - - /** - * Converts a value expressed in this unitlike form to a value expressed in - * {@code other}. - * - * @implSpec If conversion is possible, this implementation returns - * {@code other.convertFromBase(this.convertToBase(value))}. - * Therefore, overriding either of those methods will change the - * output of this method. - * - * @param other unit to convert to - * @param value value to convert - * @return converted value - * @since 2019-05-22 - * @throws IllegalArgumentException if {@code other} is incompatible for - * conversion with this unitlike form (as - * tested by {@link Unit#canConvertTo}). - * @throws NullPointerException if other is null - */ - public final double convertTo(final Unit other, final V value) { - Objects.requireNonNull(other, "other must not be null."); - if (this.canConvertTo(other)) - return other.convertFromBase(this.convertToBase(value)); - else - throw new IllegalArgumentException( - String.format("Cannot convert from %s to %s.", this, other)); - } - - /** - * Converts a value expressed in this unitlike form to a value expressed in - * {@code other}. - * - * @implSpec If conversion is possible, this implementation returns - * {@code other.convertFromBase(this.convertToBase(value))}. - * Therefore, overriding either of those methods will change the - * output of this method. - * - * @param other unitlike form to convert to - * @param value value to convert - * @param type of value to convert to - * @return converted value - * @since 2020-09-07 - * @throws IllegalArgumentException if {@code other} is incompatible for - * conversion with this unitlike form (as - * tested by {@link Unit#canConvertTo}). - * @throws NullPointerException if other is null - */ - public final W convertTo(final Unitlike other, final V value) { - Objects.requireNonNull(other, "other must not be null."); - if (this.canConvertTo(other)) - return other.convertFromBase(this.convertToBase(value)); - else - throw new IllegalArgumentException( - String.format("Cannot convert from %s to %s.", this, other)); - } - - protected abstract double convertToBase(V value); - - /** - * @return combination of units that this unit is based on - * @since 2018-12-22 - * @since v0.1.0 - */ - public final ObjectProduct getBase() { - return this.unitBase; - } - - /** - * @return dimension measured by this unit - * @since 2018-12-22 - * @since v0.1.0 - */ - public final ObjectProduct getDimension() { - if (this.dimension == null) { - final Map mapping = this.unitBase.exponentMap(); - final Map dimensionMap = new HashMap<>(); - - for (final BaseUnit key : mapping.keySet()) { - dimensionMap.put(key.getBaseDimension(), mapping.get(key)); - } - - this.dimension = ObjectProduct.fromExponentMapping(dimensionMap); - } - return this.dimension; - } - - /** - * @return the nameSymbol - * @since 2020-09-07 - */ - @Override - public final NameSymbol getNameSymbol() { - return this.nameSymbol; - } - - @Override - public String toString() { - return this.getPrimaryName().orElse("Unnamed unitlike form") - + (this.getSymbol().isPresent() - ? String.format(" (%s)", this.getSymbol().get()) - : "") - + ", derived from " - + this.getBase().toString(u -> u.getSymbol().get()) - + (this.getOtherNames().isEmpty() ? "" - : ", also called " + String.join(", ", this.getOtherNames())); - } - - /** - * @param ns name(s) and symbol to use - * @return a copy of this unitlike form with provided name(s) and symbol - * @since 2020-09-07 - * @throws NullPointerException if ns is null - */ - public Unitlike withName(final NameSymbol ns) { - return fromConversionFunctions(this.getBase(), this::convertFromBase, - this::convertToBase, - Objects.requireNonNull(ns, "ns must not be null.")); - } -} diff --git a/src/main/java/sevenUnits/unit/UnitlikeValue.java b/src/main/java/sevenUnits/unit/UnitlikeValue.java deleted file mode 100644 index ad0d1ea..0000000 --- a/src/main/java/sevenUnits/unit/UnitlikeValue.java +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (C) 2020 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package sevenUnits.unit; - -import java.util.Optional; - -import sevenUnits.utils.NameSymbol; - -/** - * - * @since 2020-09-07 - */ -final class UnitlikeValue, V> { - /** - * Gets a {@code UnitlikeValue}. - * - * @since 2020-10-02 - */ - public static , V> UnitlikeValue of(T unitlike, - V value) { - return new UnitlikeValue<>(unitlike, value); - } - - private final T unitlike; - private final V value; - - /** - * @param unitlike - * @param value - * @since 2020-09-07 - */ - private UnitlikeValue(T unitlike, V value) { - this.unitlike = unitlike; - this.value = value; - } - - /** - * @return true if this value can be converted to {@code other}. - * @since 2020-10-01 - */ - public final boolean canConvertTo(Unit other) { - return this.unitlike.canConvertTo(other); - } - - /** - * @return true if this value can be converted to {@code other}. - * @since 2020-10-01 - */ - public final boolean canConvertTo(Unitlike other) { - return this.unitlike.canConvertTo(other); - } - - /** - * Returns a UnitlikeValue that represents the same value expressed in a - * different unitlike form. - * - * @param other new unit to express value in - * @return value expressed in {@code other} - */ - public final , W> UnitlikeValue convertTo( - U other) { - return UnitlikeValue.of(other, - this.unitlike.convertTo(other, this.getValue())); - } - - /** - * Returns a UnitValue that represents the same value expressed in a - * different unit - * - * @param other new unit to express value in - * @return value expressed in {@code other} - */ - public final UnitValue convertTo(Unit other) { - return UnitValue.of(other, - this.unitlike.convertTo(other, this.getValue())); - } - - /** - * Returns this unit value represented as a {@code LinearUnitValue} with this - * unit's base unit as the base. - * - * @param ns name and symbol for the base unit, use NameSymbol.EMPTY if not - * needed. - * @since 2020-09-29 - */ - public final LinearUnitValue convertToBase(NameSymbol ns) { - final LinearUnit base = LinearUnit.getBase(this.unitlike).withName(ns); - return this.convertToLinear(base); - } - - /** - * @return a {@code LinearUnitValue} that is equivalent to this value. It - * will have zero uncertainty. - * @since 2020-09-29 - */ - public final LinearUnitValue convertToLinear(LinearUnit other) { - return LinearUnitValue.getExact(other, - this.getUnitlike().convertTo(other, this.getValue())); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof UnitlikeValue)) - return false; - final UnitlikeValue other = (UnitlikeValue) obj; - if (this.getUnitlike() == null) { - if (other.getUnitlike() != null) - return false; - } else if (!this.getUnitlike().equals(other.getUnitlike())) - return false; - if (this.getValue() == null) { - if (other.getValue() != null) - return false; - } else if (!this.getValue().equals(other.getValue())) - return false; - return true; - } - - /** - * @return the unitlike - * @since 2020-09-29 - */ - public final Unitlike getUnitlike() { - return this.unitlike; - } - - /** - * @return the value - * @since 2020-09-29 - */ - public final V getValue() { - return this.value; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + (this.getUnitlike() == null ? 0 : this.getUnitlike().hashCode()); - result = prime * result - + (this.getValue() == null ? 0 : this.getValue().hashCode()); - return result; - } - - @Override - public String toString() { - final Optional primaryName = this.getUnitlike().getPrimaryName(); - final Optional symbol = this.getUnitlike().getSymbol(); - if (primaryName.isEmpty() && symbol.isEmpty()) { - final double baseValue = this.getUnitlike() - .convertToBase(this.getValue()); - return String.format("%s unnamed unit (= %s %s)", this.getValue(), - baseValue, this.getUnitlike().getBase()); - } else { - final String unitName = symbol.orElse(primaryName.get()); - return this.getValue() + " " + unitName; - } - } -} diff --git a/src/test/java/sevenUnits/unit/MultiUnitTest.java b/src/test/java/sevenUnits/unit/MultiUnitTest.java deleted file mode 100644 index 949a1f1..0000000 --- a/src/test/java/sevenUnits/unit/MultiUnitTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (C) 2020 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package sevenUnits.unit; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - -import org.junit.jupiter.api.Test; - -/** - * Tests related to the {@code MultiUnit}. - * - * @since 2020-10-03 - */ -class MultiUnitTest { - - /** - * Ensures that the {@code MultiUnit} can convert properly. - */ - @Test - final void testConvert() { - final Random rng = ThreadLocalRandom.current(); - final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT, - BritishImperial.Length.INCH); - - assertEquals(1702.0, - footInch.convertTo(Metric.METRE.withPrefix(Metric.MILLI), - Arrays.asList(5.0, 7.0)), - 1.0); - - for (int i = 0; i < 1000; i++) { - final double feet = rng.nextInt(1000); - final double inches = rng.nextDouble() * 12; - final double millimetres = feet * 304.8 + inches * 25.4; - - final List feetAndInches = Metric.METRE - .withPrefix(Metric.MILLI).convertTo(footInch, millimetres); - assertEquals(feet, feetAndInches.get(0), 1e-10); - assertEquals(inches, feetAndInches.get(1), 1e-10); - } - } - - /** - * Test method for {@link sevenUnits.unit.MultiUnit#convertFromBase(double)}. - */ - @Test - final void testConvertFromBase() { - final Random rng = ThreadLocalRandom.current(); - final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT, - BritishImperial.Length.INCH); - - // 1.7 m =~ 5' + 7" - final List values = footInch.convertFromBase(1.7018); - - assertEquals(5, values.get(0)); - assertEquals(7, values.get(1), 1e-12); - - for (int i = 0; i < 1000; i++) { - final double feet = rng.nextInt(1000); - final double inches = rng.nextDouble() * 12; - final double metres = feet * 0.3048 + inches * 0.0254; - - final List feetAndInches = footInch.convertFromBase(metres); - assertEquals(feet, feetAndInches.get(0), 1e-10); - assertEquals(inches, feetAndInches.get(1), 1e-10); - } - } - - /** - * Test method for - * {@link sevenUnits.unit.MultiUnit#convertToBase(java.util.List)}. - */ - @Test - final void testConvertToBase() { - final Random rng = ThreadLocalRandom.current(); - final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT, - BritishImperial.Length.INCH); - - // 1.7 m =~ 5' + 7" - assertEquals(1.7018, footInch.convertToBase(Arrays.asList(5.0, 7.0)), - 1e-12); - - for (int i = 0; i < 1000; i++) { - final double feet = rng.nextInt(1000); - final double inches = rng.nextDouble() * 12; - final double metres = feet * 0.3048 + inches * 0.0254; - - assertEquals(metres, - footInch.convertToBase(Arrays.asList(feet, inches)), 1e-12); - } - } -} -- cgit v1.2.3 From 4ff3a48a659aec957c5496923a79c4487d9062ec Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 30 May 2025 20:01:53 -0500 Subject: Allow prefix search toString to be in any order The order will be determined by hashes, but it doesn't matter which one goes where. --- src/test/java/sevenUnitsGUI/PrefixSearchTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java index 305d0d7..8790315 100644 --- a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java +++ b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java @@ -18,6 +18,7 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static sevenUnitsGUI.PrefixSearchRule.COMMON_PREFIXES; import static sevenUnitsGUI.PrefixSearchRule.NO_PREFIXES; import static sevenUnitsGUI.PrefixSearchRule.getCoherentOnlyRule; @@ -150,9 +151,12 @@ class PrefixSearchTest { */ @Test final void testToString() { - assertEquals( - "Apply the following prefixes: [kilo (\u00D7 1000.0), milli (\u00D7 0.001)]", - COMMON_PREFIXES.toString()); + final String toString = COMMON_PREFIXES.toString(); + final String valid1 = "Apply the following prefixes: [kilo (\u00D7 1000.0), milli (\u00D7 0.001)]"; + final String valid2 = "Apply the following prefixes: [milli (\u00D7 0.001), kilo (\u00D7 1000.0)]"; + + assertTrue(valid1.equals(toString) || valid2.equals(toString), + "COMMON_PREFIXES.toString invalid (was \"" + toString + "\")."); } } -- cgit v1.2.3 From 7db19d307970b73559239ec343c92c7876510c2a Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 30 May 2025 20:10:09 -0500 Subject: Ensure LinearUnit&Prefix ==/hash obey contracts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, these classes' equals() and hashCode() methods did not obey the contracts: For equals(), I considered two values equal even if there was a very small deviation, in order to avoid floating-point error. This equals relation is not transitive (i.e. it is possible that a = b && b = c but a ≠ c), violating the contract of equals. This also makes it impossible to properly implement hashCode, as if two values are equal, they must have the same hash code. The solution I had provided is an ineffective hack, which could mess with hash maps and sets. I have changed the implementation to demand exact equality. I have also provided equalsApproximately() methods to both classes that use the old behaviour. Hash codes are only really used for hash maps, and the old implementation doesn't even achieve its purpose, so I did not add a method to return the old hash behaviour. --- src/main/java/sevenUnits/unit/LinearUnit.java | 15 ++++++++++++++- src/main/java/sevenUnits/unit/UnitPrefix.java | 18 +++++++++++++++++- src/main/java/sevenUnits/utils/DecimalComparison.java | 15 --------------- src/test/java/sevenUnits/unit/UnitTest.java | 8 ++++++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 3c3703c..d453a43 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -226,6 +226,19 @@ public final class LinearUnit extends Unit { if (!(obj instanceof LinearUnit)) return false; final LinearUnit other = (LinearUnit) obj; + return Objects.equals(this.getBase(), other.getBase()) + && Double.compare(this.getConversionFactor(), + other.getConversionFactor()) == 0; + } + + /** + * @return true iff this unit and other are equal, + * ignoring small differences caused by floating-point error. + * + * @apiNote This method is not transitive, + * so it cannot be used as an equals method. + */ + public boolean equalsApproximately(final LinearUnit other) { return Objects.equals(this.getBase(), other.getBase()) && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor()); @@ -247,7 +260,7 @@ public final class LinearUnit extends Unit { @Override public int hashCode() { return 31 * this.getBase().hashCode() - + DecimalComparison.hash(this.getConversionFactor()); + + Double.hashCode(this.getConversionFactor()); } /** diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index 9035969..ec4be48 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -119,6 +119,22 @@ public final class UnitPrefix implements Nameable { if (!(obj instanceof UnitPrefix)) return false; final UnitPrefix other = (UnitPrefix) obj; + return Double.compare(this.getMultiplier(), + other.getMultiplier()) == 0; + } + + /** + * @return true iff this prefix and other are equal, + * ignoring small differences caused by floating-point error. + * + * @apiNote This method is not transitive, + * so it cannot be used as an equals method. + */ + public boolean equalsApproximately(final UnitPrefix other) { + if (this == other) + return true; + if (other == null) + return false; return DecimalComparison.equals(this.getMultiplier(), other.getMultiplier()); } @@ -143,7 +159,7 @@ public final class UnitPrefix implements Nameable { */ @Override public int hashCode() { - return DecimalComparison.hash(this.getMultiplier()); + return Double.hashCode(this.getMultiplier()); } /** diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index 62c3720..03dd15b 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -69,7 +69,6 @@ public final class DecimalComparison { * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 - * @see #hash(double) */ public static final boolean equals(final double a, final double b) { return DecimalComparison.equals(a, b, DOUBLE_EPSILON); @@ -194,7 +193,6 @@ public final class DecimalComparison { * @param b second value to test * @return whether they are equal * @since 2020-09-07 - * @see #hash(double) */ public static final boolean equals(final UncertainDouble a, final UncertainDouble b) { @@ -236,19 +234,6 @@ public final class DecimalComparison { epsilon); } - /** - * Takes the hash code of doubles. Values that are equal according to - * {@link #equals(double, double)} will probably have the same hash code. - * - * @param d double to hash - * @return hash code of double - * @since 2019-10-16 - */ - // TODO reconsider using this - 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/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index 6ac0ebd..f8d3040 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -48,8 +48,12 @@ class UnitTest { final LinearUnit foot = Metric.METRE.times(0.3048) .withName(NameSymbol.of("foot", "ft")); - assertEquals(inch.plus(foot), Metric.METRE.times(0.3302)); - assertEquals(foot.minus(inch), Metric.METRE.times(0.2794)); + assertTrue(inch.plus(foot).equalsApproximately(Metric.METRE.times(0.3302)), + String.format("Expected: %s, Actual: %s", + inch.plus(foot), Metric.METRE.times(0.3302))); + assertTrue(foot.minus(inch).equalsApproximately(Metric.METRE.times(0.2794)), + String.format("Expected: %s, Actual: %s", + foot.minus(inch), Metric.METRE.times(0.2794))); // test with LinearUnitValue final LinearUnitValue value1 = LinearUnitValue.getExact(Metric.METRE, 15); -- cgit v1.2.3 From 7812a0f6a219cb817a5e6b9db34012f7eca373b8 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 30 May 2025 20:26:22 -0500 Subject: Update roadmap --- docs/roadmap.org | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/roadmap.org b/docs/roadmap.org index 963c17d..fdd12ac 100644 --- a/docs/roadmap.org +++ b/docs/roadmap.org @@ -1,11 +1,6 @@ * Version 1.0.0 Roadmap Here is a list of the unfinished requirements for version 1.0.0. When everything here is met, I intend to release version 1.0.0 and consider 7Units complete (for the most part). -These requirements are subject to change. I intend to finish version 1.0.0 by [2025-04-27 Sun]. +These requirements are subject to change. I intend to finish version 1.0.0 by [2025-06-01 Sun]. -Documentation/Testing Requirements: -- 7Units should be fully documented. -- 7Units should have automated testing with a code coverage of at least 2/3 (ideally at least 5/6). - -Other Requirements -- The public API of 7Units should be finalized and well-designed. +Once the documentation is up to date, 7Units 1.0.0 can be released. -- cgit v1.2.3 From 7eb1ce7787c6306321a4db378b1cbd37ce721583 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 30 May 2025 20:31:09 -0500 Subject: Bump version to 1.0.0b2 All that's left before 1.0.0 is released is the documentation fixes. Unless I find any major issues, 7Units 1.0.0 releases June 8. --- README.org | 2 +- src/main/java/sevenUnits/ProgramInfo.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.org b/README.org index 279c378..aaec93b 100644 --- a/README.org +++ b/README.org @@ -1,4 +1,4 @@ -* 7Units Version 1.0.0-beta.1 +* 7Units Version 1.0.0-beta.2 (this project uses Semantic Versioning) ** What is it? This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in. diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index fee3cea..4fd4375 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -26,9 +26,9 @@ import sevenUnits.utils.SemanticVersionNumber; */ public final class ProgramInfo { - /** The version number (1.0.0-beta.1) */ + /** The version number (1.0.0-beta.2) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber - .preRelease(1, 0, 0, "beta", 1); + .preRelease(1, 0, 0, "beta", 2); private ProgramInfo() { // this class is only for static variables, you shouldn't be able to -- cgit v1.2.3 From 9fae9ea92fafeaade20e0641a768c30c22ca79a4 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 1 Jun 2025 19:37:14 -0500 Subject: Update README --- README.org | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/README.org b/README.org index aaec93b..ff46ae2 100644 --- a/README.org +++ b/README.org @@ -1,18 +1,35 @@ * 7Units Version 1.0.0-beta.2 - (this project uses Semantic Versioning) +(this project uses Semantic Versioning) + +[[http://unmaintained.tech/][http://unmaintained.tech/badge.svg]] ** What is it? - This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in. +This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in. + +7Units started as a high school project, but over my bachelor's degree I continued working on it from time to time to perfect it. - Check the [[./docs/manual.pdf][user manual]] for information on how to use the program. +Check the [[./docs/manual.pdf][user manual]] for information on how to use the program. ** Features - - Convert between units and between expressions of units - - Any linear or base units can use unit prefixes (including non-metric units!) - - Units and prefixes are defined in an editable data file, in a simple and intuitive format. - - Viewer and Prefix Viewer which allow you to search through all of the available units and prefixes and learn details about them. - - All of SI included in default text file - - Customizable rounding rules +- Convert between units and between expressions of units +- Any linear or base units can use unit prefixes (including non-metric units!) +- Units and prefixes are defined in an editable data file, in a simple and intuitive format. +- Viewer and Prefix Viewer which allow you to search through all of the available units and prefixes and learn details about them. +- All of SI included in default text file +- Customizable rounding rules +- Multiple languages, with ability to add additional custom language files ** Screenshots - #+CAPTION: The main conversion interface - [[./screenshots/main-interface-dimension-converter.png]] - #+CAPTION: A secondary, more complicated conversion option - [[./screenshots/main-interface-expression-converter.png]] +#+CAPTION: The main conversion interface +[[./screenshots/main-interface-dimension-converter.png]] +#+CAPTION: A secondary, more complicated conversion option +[[./screenshots/main-interface-expression-converter.png]] +** How to Use 7Units +7Units uses Gradle as its build system. + +- To build 7Units: ~./gradlew build~. + This creates a test report at ~build/reports/tests/test/index.html~ and a Jacoco code coverage report at ~build/reports/jacoco/test/html/index.html~. +- To run 7Units: ~./gradlew run~. +- To generate Javadoc: ~./gradlew javadoc~. + The Javadoc will be at ~build/docs/javadoc/index.html~. +- To create a JAR: ~./gradlew jar~. + The JAR will be at ~build/libs/sevenUnits.jar~. + +Instructions on using the program itself can be found at ~docs/manual.pdf~. -- cgit v1.2.3 From 7c54ea36d7273e010b7603cfbf09a87fc23a82b4 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 1 Jun 2025 19:56:05 -0500 Subject: Save & load default datafile use setting --- src/main/java/sevenUnitsGUI/Presenter.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index e383b2c..44261a5 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -1057,6 +1057,9 @@ public final class Presenter { case "search_prefix_rule": this.setSearchRuleFromString(value); break; + case "use_default_datafiles": + this.useDefaultDatafiles = Boolean.parseBoolean(value); + break; case "locale": if (this.locales.containsKey(value)) { this.userLocale = value; @@ -1421,6 +1424,8 @@ public final class Presenter { String.format("include_duplicates=%s\n", this.showDuplicates)); writer.write(String.format("search_prefix_rule=%s\n", searchRuleToString(this.searchRule))); + writer.write( + String.format("use_default_datafiles=%s\n", this.useDefaultDatafiles)); writer.write(String.format("locale=%s\n", this.userLocale)); return true; } catch (final IOException e) { -- cgit v1.2.3 From ae0559a9432f85f9147eeb80b35f1b2947889acd Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 1 Jun 2025 20:05:18 -0500 Subject: Update documentation to version 1.0.0 --- docs/data_spec.org | 8 ++- docs/data_spec.pdf | Bin 90604 -> 92033 bytes docs/data_spec.tex | 27 ++++++----- docs/design.org | 5 +- docs/design.pdf | Bin 352153 -> 350221 bytes docs/design.tex | 73 ++++++++++++++-------------- docs/manual.org | 140 ++++++++++++++++++++++++++++------------------------- docs/manual.pdf | Bin 185625 -> 186518 bytes docs/manual.tex | 48 ++++++++++-------- docs/roadmap.org | 6 --- 10 files changed, 160 insertions(+), 147 deletions(-) delete mode 100644 docs/roadmap.org diff --git a/docs/data_spec.org b/docs/data_spec.org index c75eaa7..d251230 100644 --- a/docs/data_spec.org +++ b/docs/data_spec.org @@ -1,6 +1,6 @@ #+TITLE: 7Units Datafile Specification -#+SUBTITLE: For Version 0.5.0 -#+DATE: 2024 March 23 +#+SUBTITLE: For Version 1.0.0 +#+DATE: 2025 June 1 #+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} #+LaTeX: \newpage @@ -35,5 +35,9 @@ If a line's name part ends in the ASCII dash (~-~, 0x2D), it defines a prefix in Dimension files give names to unit dimensions, so they can be selected in the unit converter to determine which units are shown. Dimension files are similar to unit and prefix files, except that they use a different set of base /dimensions/ (defined with ! as usual), and that addition and subtraction are not supported in dimension expressions. * Metric Exception Files Metric exception files list exceptions to the One-Way Conversion rule. Units included in these files are always shown on both sides of the unit converter, even if One Way Conversion is enabled. They are just a list of units, one per line. Ignored lines and comments work the same way as in other data files. +* Locale Files +A locale file contains the data to translate 7Units into another language. Each line is in the form ~key=value~. A sample locale file, with all available keys, can be found in ~src/resources/locales/en.txt~. + +To translate 7Units, simply create another locale file in the ~locales~ subdirectory of your 7Units configuration directory. * Configuration Files A configuration file contains one line for each configuration setting, in the format ~key=value~. Check the user manual for the list of configurable setting keys. Ignored lines and comments work the same way as in other data files. diff --git a/docs/data_spec.pdf b/docs/data_spec.pdf index 35e01f8..cae6592 100644 Binary files a/docs/data_spec.pdf and b/docs/data_spec.pdf differ diff --git a/docs/data_spec.tex b/docs/data_spec.tex index 879d62c..4e2475d 100644 --- a/docs/data_spec.tex +++ b/docs/data_spec.tex @@ -1,4 +1,4 @@ -% Created 2024-03-24 Sun 13:16 +% Created 2025-06-01 Sun 19:42 % Intended LaTeX compiler: pdflatex \documentclass[11pt]{article} \usepackage[utf8]{inputenc} @@ -13,15 +13,15 @@ \usepackage{capt-of} \usepackage{hyperref} \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} -\date{2024 March 23} +\date{2025 June 1} \title{7Units Datafile Specification\\\medskip -\large For Version 0.5.0} +\large For Version 1.0.0} \hypersetup{ pdfauthor={}, pdftitle={7Units Datafile Specification}, pdfkeywords={}, pdfsubject={}, - pdfcreator={Emacs 29.2 (Org mode 9.6.15)}, + pdfcreator={Emacs 29.3 (Org mode 9.6.15)}, pdflang={English}} \begin{document} @@ -30,7 +30,7 @@ \newpage \section{Unit and Prefix Files} -\label{sec:org3f0bfdf} +\label{sec:orgc529fa1} Unit and prefix files specify the units and prefixes that are available to 7Units. Their format is intended to be compatible with 7Units's inspiration, \href{https://www.gnu.org/software/units/}{GNU Units}. Each unit or prefix is specified by a line in the following format: @@ -40,7 +40,7 @@ Each unit or prefix is specified by a line in the following format: The name may not contain whitespace, but the definition can and often will. Lines can be separated by either a line feed (0x0A) or a carriage return followed by a line feed (0x0D 0x0A). \subsection{Ignored Lines \& Comments} -\label{sec:org7396c6b} +\label{sec:orga9c97a7} All of the following should be ignored: \begin{itemize} \item Any line containing only whitespace @@ -50,7 +50,7 @@ All of the following should be ignored: This allows unit and prefix files to be easily organized and divided. \subsection{Unit Expressions} -\label{sec:orga56737c} +\label{sec:orgebaa825} The definition part of a unit's line is a unit expression - the same sort of expression you would use in the complex unit converter. Expressions should be a standard mathematical expression, which can operate on either numbers or units. The following operators are supported: addition (+), subtraction (-), multiplication (\texttt{*} or no operator), division (\texttt{/} or \texttt{|}), exponentation (\texttt{\textasciicircum{}}; exponent must be a number). Brackets (\texttt{(} and \texttt{)}) may be used to change order of operations, but otherwise standard BEDMAS order is followed (exponentation first, then multiplication and division, then addition and subtraction), with two exceptions: if a number is multiplied by a unit using spaces, the multiplication will have precedence over division, and division with \texttt{|} has higher precedence than any other operator. For example, "2 m / 1 m" is equal to the dimensionless value 2, not 2 m\textsuperscript{2}. An example of a line defining a unit is: @@ -60,15 +60,20 @@ yard 9 dm + 1.4 cm + 4 mm^2 / 10 mm If the definition is an exclamation mark (\texttt{!}), this defines a base unit, which is expected to already be stored in the system. \subsection{Prefixes \& Prefix Expressions} -\label{sec:org28c98b9} +\label{sec:orgd0ef1d8} If a line's name part ends in the ASCII dash (\texttt{-}, 0x2D), it defines a prefix instead of a unit (this dash is not included in the prefix's name). Prefix expressions are like unit expressions, but the operands are prefixes and numbers, not units and numbers. Because prefixes do not have a dimension, there are no base prefixes - the exclamation mark is forbidden in prefix expressions. \section{Dimension Files} -\label{sec:org9530301} +\label{sec:orgdc0ddd9} Dimension files give names to unit dimensions, so they can be selected in the unit converter to determine which units are shown. Dimension files are similar to unit and prefix files, except that they use a different set of base \emph{dimensions} (defined with ! as usual), and that addition and subtraction are not supported in dimension expressions. \section{Metric Exception Files} -\label{sec:org79afe10} +\label{sec:org0587104} Metric exception files list exceptions to the One-Way Conversion rule. Units included in these files are always shown on both sides of the unit converter, even if One Way Conversion is enabled. They are just a list of units, one per line. Ignored lines and comments work the same way as in other data files. +\section{Locale Files} +\label{sec:orgdc95be9} +A locale file contains the data to translate 7Units into another language. Each line is in the form \texttt{key=value}. A sample locale file, with all available keys, can be found in \texttt{src/resources/locales/en.txt}. + +To translate 7Units, simply create another locale file in the \texttt{locales} subdirectory of your 7Units configuration directory. \section{Configuration Files} -\label{sec:orgf3f6b9a} +\label{sec:orgad1bd81} A configuration file contains one line for each configuration setting, in the format \texttt{key=value}. Check the user manual for the list of configurable setting keys. Ignored lines and comments work the same way as in other data files. \end{document} diff --git a/docs/design.org b/docs/design.org index 65ee651..3764adb 100644 --- a/docs/design.org +++ b/docs/design.org @@ -1,6 +1,6 @@ #+TITLE: 7Units Design Document -#+SUBTITLE: For version 0.5.0 -#+DATE: 2024 March 23 +#+SUBTITLE: For version 1.0.0 +#+DATE: 2025 June 1 #+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} #+LaTeX_HEADER: \usepackage{xurl} #+LaTeX: \newpage @@ -66,7 +66,6 @@ - Note that any operations will return a unit without name(s) or a symbol. All unit classes have a ~withName~ method that returns a copy of them with different names and/or a different symbol (all of this info is contained in the ~NameSymbol~ class) There are a few more classes which play small roles in the unit system: - - Unitlike :: A class that is like a unit, but its "value" can be any class. The only use of this class right now is to implement ~MultiUnit~, a combination of units (like "foot + inch", commonly used in North America for measuring height); its "value" is a list of numbers. - FunctionalUnit :: A convenience class that implements the two conversion functions of ~Unit~ using ~DoubleUnaryOperator~ instances. This is used internally to implement degrees Celsius and Fahrenheit. There is also a version of this for ~Unitlike~, ~FunctionalUnitlike~. - UnitValue :: A value expressed as a certain unit (such as "7 inches"). This class is used by the simple unit converter to represent units. You can convert them between units. There are also versions of this for ~LinearUnit~ and ~Unitlike~. - Metric :: A static utility class with instances of all of the SI named units, the 9 base dimensions, SI prefixes, some common prefixed units like the kilometre, and a few non-SI units used commonly with them. diff --git a/docs/design.pdf b/docs/design.pdf index 00703d8..764bf3b 100644 Binary files a/docs/design.pdf and b/docs/design.pdf differ diff --git a/docs/design.tex b/docs/design.tex index c850412..9779a9e 100644 --- a/docs/design.tex +++ b/docs/design.tex @@ -1,4 +1,4 @@ -% Created 2024-03-24 Sun 13:15 +% Created 2025-06-01 Sun 19:45 % Intended LaTeX compiler: pdflatex \documentclass[11pt]{article} \usepackage[utf8]{inputenc} @@ -14,15 +14,15 @@ \usepackage{hyperref} \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} \usepackage{xurl} -\date{2024 March 23} +\date{2025 June 1} \title{7Units Design Document\\\medskip -\large For version 0.5.0} +\large For version 1.0.0} \hypersetup{ pdfauthor={}, pdftitle={7Units Design Document}, pdfkeywords={}, pdfsubject={}, - pdfcreator={Emacs 29.2 (Org mode 9.6.15)}, + pdfcreator={Emacs 29.3 (Org mode 9.6.15)}, pdflang={English}} \begin{document} @@ -32,35 +32,35 @@ \newpage \section{Introduction} -\label{sec:orgdac8c13} +\label{sec:orge8f18c8} 7Units is a program that can convert between units. This document details the internal design of 7Units, intended to be used by current and future developers. \section{System Overview} -\label{sec:org2a8e77a} +\label{sec:org3dec32e} \begin{figure}[htbp] \centering \includegraphics[height=144px]{./diagrams/overview-diagram.plantuml.png} \caption{A big-picture diagram of 7Units, containing all of the major classes.} \end{figure} \subsection{Packages of 7Units} -\label{sec:org80ac84e} +\label{sec:org5f2f6d8} 7Units splits its code into three main packages: \begin{description} -\item[{\texttt{sevenUnits.unit}}] The \hyperref[sec:orgc2400d6]{unit system} +\item[{\texttt{sevenUnits.unit}}] The \hyperref[sec:orgdc040fb]{unit system} \item[{\texttt{sevenUnits.utils}}] Extra classes that aid the unit system. -\item[{\texttt{sevenUnitsGUI}}] The \hyperref[sec:org261b06e]{front end} code +\item[{\texttt{sevenUnitsGUI}}] The \hyperref[sec:org1879a96]{front end} code \end{description} \texttt{sevenUnits.unit} depends on \texttt{sevenUnits.utils}, while \texttt{sevenUnitsGUI} depends on both \texttt{sevenUnits} packages. There is only one class that isn't in any of these packages, \texttt{sevenUnits.VersionInfo}. \subsection{Major Classes of 7Units} -\label{sec:org5910307} +\label{sec:orgea198c1} \begin{description} -\item[{\hyperref[sec:org946a4e5]{sevenUnits.unit.Unit}}] The class representing a unit -\item[{\hyperref[sec:orgac71770]{sevenUnits.unit.UnitDatabase}}] A class that stores collections of units, prefixes and dimensions. -\item[{\hyperref[sec:org57b8a42]{sevenUnitsGUI.View}}] The class that handles interaction between the user and the program. -\item[{\hyperref[sec:orga668171]{sevenUnitsGUI.Presenter}}] The class that handles communication between the \texttt{View} and the unit system. +\item[{\hyperref[sec:org097d2bc]{sevenUnits.unit.Unit}}] The class representing a unit +\item[{\hyperref[sec:orgdd255ff]{sevenUnits.unit.UnitDatabase}}] A class that stores collections of units, prefixes and dimensions. +\item[{\hyperref[sec:org1047a59]{sevenUnitsGUI.View}}] The class that handles interaction between the user and the program. +\item[{\hyperref[sec:org17c3fce]{sevenUnitsGUI.Presenter}}] The class that handles communication between the \texttt{View} and the unit system. \end{description} \newpage \subsection{Process of Unit Conversion} -\label{sec:orgbbad9d5} +\label{sec:orgfe8e937} \begin{figure}[htbp] \centering \includegraphics[width=.9\linewidth]{./diagrams/convert-units.plantuml.png} @@ -75,7 +75,7 @@ \end{enumerate} \newpage \subsection{Process of Expression Conversion} -\label{sec:org52749d5} +\label{sec:orgbdb360e} The process of expression conversion is similar to that of unit conversion. \begin{figure}[htbp] \centering @@ -91,7 +91,7 @@ The process of expression conversion is similar to that of unit conversion. \end{enumerate} \newpage \section{Unit System Design} -\label{sec:orgc2400d6} +\label{sec:orgdc040fb} Any code related to the backend unit system is stored in the \texttt{sevenUnits.unit} package. Here is a class diagram of the system. Unimportant methods, methods inherited from Object, getters and setters have been omitted. @@ -102,11 +102,11 @@ Here is a class diagram of the system. Unimportant methods, methods inherited f \end{figure} \newpage \subsection{Dimensions} -\label{sec:orgb21aaed} -Dimensions represent what a unit is measuring, such as length, time, or energy. Dimensions are represented as an \hyperref[sec:org16bc96f]{ObjectProduct}, where \texttt{BaseDimension} is a very simple class (its only properties are a name and a symbol) which represents the dimension of a base unit; these base dimensions can be multiplied to create all other Dimensions. +\label{sec:orgd0c0232} +Dimensions represent what a unit is measuring, such as length, time, or energy. Dimensions are represented as an \hyperref[sec:org5bd9128]{ObjectProduct}, where \texttt{BaseDimension} is a very simple class (its only properties are a name and a symbol) which represents the dimension of a base unit; these base dimensions can be multiplied to create all other Dimensions. \subsection{Unit Classes} -\label{sec:org946a4e5} -Units are internally represented by the abstract class \texttt{Unit}. All units have an \hyperref[sec:org16bc96f]{ObjectProduct} (referred to as the base) that they are based on, a dimension (ObjectProduct), one or more names and a symbol (these last two bits of data are contained in the \texttt{NameSymbol} class). The dimension is calculated from the base unit when needed; the variable is just a cache. It has two constructors: a package-private one used to make \texttt{BaseUnit} instances, and a protected one used to make general units (for other subclasses of \texttt{Unit}). All unit classes are immutable. +\label{sec:org097d2bc} +Units are internally represented by the abstract class \texttt{Unit}. All units have an \hyperref[sec:org5bd9128]{ObjectProduct} (referred to as the base) that they are based on, a dimension (ObjectProduct), one or more names and a symbol (these last two bits of data are contained in the \texttt{NameSymbol} class). The dimension is calculated from the base unit when needed; the variable is just a cache. It has two constructors: a package-private one used to make \texttt{BaseUnit} instances, and a protected one used to make general units (for other subclasses of \texttt{Unit}). All unit classes are immutable. Units also have two conversion functions - one which converts from a value expressed in this unit to its base unit, and another which converts from a value expressed in the base unit to this unit. In \texttt{Unit}, they are defined as two abstract methods. This allows you to convert from any unit to any other (as long as they have the same base, i.e. you aren't converting metres to pounds). To convert from A to B, first convert from A to its base, then convert from the base to B. @@ -123,7 +123,6 @@ However, most units are instances of \texttt{LinearUnit}, another subclass of \t There are a few more classes which play small roles in the unit system: \begin{description} -\item[{Unitlike}] A class that is like a unit, but its "value" can be any class. The only use of this class right now is to implement \texttt{MultiUnit}, a combination of units (like "foot + inch", commonly used in North America for measuring height); its "value" is a list of numbers. \item[{FunctionalUnit}] A convenience class that implements the two conversion functions of \texttt{Unit} using \texttt{DoubleUnaryOperator} instances. This is used internally to implement degrees Celsius and Fahrenheit. There is also a version of this for \texttt{Unitlike}, \texttt{FunctionalUnitlike}. \item[{UnitValue}] A value expressed as a certain unit (such as "7 inches"). This class is used by the simple unit converter to represent units. You can convert them between units. There are also versions of this for \texttt{LinearUnit} and \texttt{Unitlike}. \item[{Metric}] A static utility class with instances of all of the SI named units, the 9 base dimensions, SI prefixes, some common prefixed units like the kilometre, and a few non-SI units used commonly with them. @@ -131,20 +130,20 @@ There are a few more classes which play small roles in the unit system: \item[{USCustomary}] A static utility class with instances of common units in the US Customary system (not to be confused with the British Imperial system; it has the same unit names but the values of a few units are different). \end{description} \subsection{Prefixes} -\label{sec:orgede1b85} +\label{sec:orge1a285c} A \texttt{UnitPrefix} is a simple object that can multiply a \texttt{LinearUnit} by a value. It can calculate a new name for the unit by combining its name and the unit's name (symbols are done similarly). It can do multiplication, division and exponentation with a number, as well as multiplication and division with another prefix; all of these work by changing the prefix's multiplier. \subsection{The Unit Database} -\label{sec:orgac71770} +\label{sec:orgdd255ff} The \texttt{UnitDatabase} class stores all of the unit, prefix and dimension data used by this program. It is not a representation of an actual database, just a class that stores lots of data. Units are stored using a custom \texttt{Map} implementation (\texttt{PrefixedUnitMap}) which maps unit names to units. It is backed by two maps: one for units (without prefixes) and one for prefixes. It is programmed to include prefixes (so if units includes "metre" and prefixes includes "kilo", this map will include "kilometre", mapping it to a unit representing a kilometre). It is immutable, but you can modify the underlying maps, which is reflected in the \texttt{PrefixedUnitMap}. Other than that, it is a normal map implementation. Prefixes and dimensions are stored in normal maps. \subsubsection{Parsing Expressions} -\label{sec:org02e3ff1} -Each \texttt{UnitDatabase} instance has four \hyperref[sec:org7296a14]{ExpressionParser} instances associated with it, for four types of expressions: unit, unit value, prefix and dimension. They are mostly similar, with operators corresponding to each operation of the corresponding class (\texttt{LinearUnit}, \texttt{LinearUnitValue}, \texttt{UnitPrefix}, \texttt{ObjectProduct}). Unit and unit value expressions use linear units; nonlinear units can be used with a special syntax (like "degC(20)") and are immediately converted to a linear unit representing their base (Kelvin in this case) before operating. +\label{sec:orgd4e45a3} +Each \texttt{UnitDatabase} instance has four \hyperref[sec:org6a2c74c]{ExpressionParser} instances associated with it, for four types of expressions: unit, unit value, prefix and dimension. They are mostly similar, with operators corresponding to each operation of the corresponding class (\texttt{LinearUnit}, \texttt{LinearUnitValue}, \texttt{UnitPrefix}, \texttt{ObjectProduct}). Unit and unit value expressions use linear units; nonlinear units can be used with a special syntax (like "degC(20)") and are immediately converted to a linear unit representing their base (Kelvin in this case) before operating. \subsubsection{Parsing Files} -\label{sec:org676148d} +\label{sec:org6cff6b9} There are two types of data files: unit and dimension. Unit files contain data about units and prefixes. Each line contains the name of a unit or prefix (prefixes end in a dash, units don't) followed by an expression which defines it, separated by one or more space characters (this behaviour is defined by the static regular expression \texttt{NAME\_EXPRESSION}). Unit files are parsed line by line, each line being run through the \texttt{addUnitOrPrefixFromLine} method, which splits a line into name and expression, determines whether it's a unit or a prefix, and parses the expression. Because all units are defined by others, base units need to be defined with a special expression "!"; \textbf{these units should be added to the database before parsing the file}. @@ -152,10 +151,10 @@ Unit files contain data about units and prefixes. Each line contains the name o Dimension files are similar, only for dimensions instead of units and prefixes. \newpage \section{Front-End Design} -\label{sec:org261b06e} +\label{sec:org1879a96} The front-end of 7Units is based on an MVP model. There are two major frontend classes, the \textbf{View} and the \textbf{Presenter}. \subsection{The View} -\label{sec:org57b8a42} +\label{sec:org1047a59} The \texttt{View} is the part of the frontend code that directly interacts with the user. It handles input and output, but does not do any processing. Processing is handled by the presenter and the backend code. The \texttt{View} is an interface, not a single class, so that I can easily create multiple views without having to rewrite any processing code. This allows me to easily prototype changes to the GUI without messing with existing code. @@ -169,10 +168,10 @@ There are currently two implementations of the \texttt{View}: \end{description} Both of these \texttt{View} implementations implement \texttt{UnitConversionView} and \texttt{ExpressionConversionView}. \subsection{The Presenter} -\label{sec:orga668171} +\label{sec:org17c3fce} The \texttt{Presenter} is an intermediary between the \texttt{View} and the backend code. It accepts the user's input and passes it to the backend, then accepts the backend's output and passes it to the frontend for user viewing. Its main functions do not have arguments or return values; instead it takes input from and provides output to the \texttt{View} via its public methods. \subsubsection{Rules} -\label{sec:org81f6f8a} +\label{sec:org159ba12} The \texttt{Presenter} has a set of function-object rules that determine some of its behaviours. Each corresponds to a setting in the \texttt{View}, but they can be set to other values via the \texttt{Presenter}'s setters (although nonstandard rules cannot be saved and loaded): \begin{description} \item[{numberDisplayRule}] A function that determines how numbers are displayed. This controls the rounding rules. @@ -182,7 +181,7 @@ The \texttt{Presenter} has a set of function-object rules that determine some of These rules have been made this way to enable an incredible level of customization of these behaviours. Because any function object with the correct arguments and return type is accepted, these rules (especially the number display rule) can do much more than the default behaviours. \subsection{Utility Classes} -\label{sec:orga5b57ce} +\label{sec:org4d2c502} The frontend has many miscellaneous utility classes. Many of them are package-private. Here is a list of them, with a brief description of what they do and where they are used: \begin{description} \item[{DefaultPrefixRepetitionRule}] An enum containing the available rules determining when you can repeat prefixes. Used by the \texttt{TabbedView} for selecting the rule and by the \texttt{Presenter} for loading it from a file. @@ -195,15 +194,15 @@ The frontend has many miscellaneous utility classes. Many of them are package-p \end{description} \newpage \section{Utility Classes} -\label{sec:org3686e64} +\label{sec:org97c6f74} 7Units has a few general "utility" classes. They aren't directly related to units, but are used in the units system. \subsection{ObjectProduct} -\label{sec:org16bc96f} +\label{sec:org5bd9128} An \texttt{ObjectProduct} represents a "product" of elements of some type. The units system uses them to represent coherent units as a product of base units, and dimensions as a product of base dimensions. Internally, it is represented using a map mapping objects to their exponents in the product. For example, the unit "kg m\textsuperscript{2} / s\textsuperscript{2}" (i.e. a Joule) would be represented with a map like \texttt{[kg: 1, m: 2, s: -2]}. \subsection{ExpressionParser} -\label{sec:org7296a14} +\label{sec:org6a2c74c} The \texttt{ExpressionParser} class is used to parse the unit, prefix and dimension expressions that are used throughout 7Units. An expression is something like "(2 m + 30 J / N) * 8 s)". Each instance represents a type of expression, containing a way to obtain values (such as numbers or units) from the text and operations that can be done on these values (such as addition, subtraction or multiplication). Each operation also has a priority, which controls the order of operations (i.e. multiplication gets a higher priority than addition). \texttt{ExpressionParser} has a parameterized type \texttt{T}, which represents the type of the value used in the expression. The expression parser currently only supports one type of value per expression; in the expressions used by 7Units numbers are treated as a kind of unit or prefix. Operators are represented by internal types; the system distinguishes between unary operators (those that take a single value, like negation) and binary operators (those that take 2 values, like +, -, * or /). @@ -224,13 +223,13 @@ Expressions are parsed in 2 steps: After evaluating the last token, there should be one value left in the stack - the answer. If there isn't, the original expression was malformed. \end{enumerate} \subsection{Math Classes} -\label{sec:orgfd8c723} +\label{sec:orgfec3253} There are two simple math classes in 7Units: \begin{description} \item[{\texttt{UncertainDouble}}] Like a \texttt{double}, but with an uncertainty (e.g. \(2.0 \pm 0.4\)). The operations are like those of the regular Double, only they also calculate the uncertainty of the final value. They also have "exact" versions to help interoperation between \texttt{double} and \texttt{UncertainDouble}. It is used by the converter's Scientific Precision setting. \item[{\texttt{DecimalComparison}}] A static utility class that contains a few alternate equals() methods for \texttt{double} and \texttt{UncertainDouble}. These methods allow a slight (configurable) difference between values to still be considered equal, to fight roundoff error. \end{description} \subsection{Collection Classes} -\label{sec:org32d7d09} +\label{sec:orgc8ae9c7} The \texttt{ConditionalExistenceCollections} class contains wrapper implementations of \texttt{Collection}, \texttt{Iterator}, \texttt{Map} and \texttt{Set}. These implementations ignore elements that do not pass a certain condition - if an element fails the condition, \texttt{contains} will return false, the iterator will skip past it, it won't be counted in \texttt{size}, etc. even if it exists in the original collection. Effectively, any element of the original collection that fails the test does not exist. \end{document} diff --git a/docs/manual.org b/docs/manual.org index 3c6de1c..92160c3 100644 --- a/docs/manual.org +++ b/docs/manual.org @@ -1,78 +1,81 @@ #+TITLE: 7Units User Manual -#+SUBTITLE: For Version 0.5.0 -#+DATE: 2024 March 23 +#+SUBTITLE: For Version 1.0.0 +#+DATE: 2025 June 1 #+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} #+LaTeX: \newpage * Introduction and Purpose - 7Units is a program that can be used to convert units. This document outlines how to use the program. +7Units is a program that can be used to convert units. This document outlines how to use the program. * System Requirements - - Works on all major operating systems \\ - *NOTE:* All screenshots in this document were taken on Windows 10. If you use a different operating system, the program will probably look different than what is shown. - - Java version 11+ required -# installation instructions go here - wait until git repository is fixed/set up +- Works on all major operating systems \\ + *NOTE:* All screenshots in this document were taken on Windows 10. If you use a different operating system, the program will probably look different than what is shown. +- Java version 11+ required +- Gradle required +- To run the software, simply run './gradlew run' in the main directory. #+LaTeX: \newpage * How to Use 7Units ** Simple Unit Conversion - 1. Select the "Convert Units" tab if it is not already selected. You should see a screen like in figure [[main-interface-dimension]]: - #+CAPTION: Taken in version 0.3.0 - #+ATTR_LaTeX: :height 250px - #+name: main-interface-dimension - [[../screenshots/main-interface-dimension-converter.png]] - 2. Use the dropdown box at the top to select what kind of unit to convert (length, mass, speed, etc.) - 3. Select the unit to convert /from/ on the left. - 4. Select the unit to convert /to/ on the right. - 5. Enter the value to convert in the box above the convert button. The program should look something like in figure [[sample-conversion-dimension]]: - #+CAPTION: This image, taken in version 0.3.0, shows the user about to convert 35 miles to kilometres. - #+attr_latex: :height 250px - #+name: sample-conversion-dimension - [[../screenshots/sample-conversion-dimension-converter.png]] - 6. Press the "Convert" button. The result will be shown below the "Convert" button. This is shown in figure [[sample-results-dimension]] - #+CAPTION: The result of the above conversion - #+attr_latex: :height 250px - #+name: sample-results-dimension - [[../screenshots/sample-conversion-results-dimension-converter.png]] +1. Select the "Convert Units" tab if it is not already selected. You should see a screen like in figure [[main-interface-dimension]]: + #+CAPTION: Taken in version 0.3.0 + #+ATTR_LaTeX: :height 250px + #+name: main-interface-dimension + [[../screenshots/main-interface-dimension-converter.png]] +2. Use the dropdown box at the top to select what kind of unit to convert (length, mass, speed, etc.) +3. Select the unit to convert /from/ on the left. +4. Select the unit to convert /to/ on the right. +5. Enter the value to convert in the box above the convert button. The program should look something like in figure [[sample-conversion-dimension]]: + #+CAPTION: This image, taken in version 0.3.0, shows the user about to convert 35 miles to kilometres. + #+attr_latex: :height 250px + #+name: sample-conversion-dimension + [[../screenshots/sample-conversion-dimension-converter.png]] +6. Press the "Convert" button. The result will be shown below the "Convert" button. This is shown in figure [[sample-results-dimension]] + #+CAPTION: The result of the above conversion + #+attr_latex: :height 250px + #+name: sample-results-dimension + [[../screenshots/sample-conversion-results-dimension-converter.png]] ** Complex Unit Conversion - 1. Select the "Convert Unit Expressions" if it is not already selected. You should see a screen like in figure [[main-interface-expression]]: - #+CAPTION: Taken in version 0.3.0 - #+attr_latex: :height 250px - #+name: main-interface-expression - [[../screenshots/main-interface-expression-converter.png]] - 2. Enter a [[*Unit Expressions][unit expression]] in the From box. This can be something like "~7 km~" or "~6 ft - 2 in~" or "~3 kg m + 9 lb ft + (35 mm)^2 * (85 oz) / (20 in)~". - 3. Enter a unit name (or another unit expression) in the To box. - 4. Press the Convert button. This will calculate the value of the first expression, and convert it to a multiple of the second unit (or expression). - #+CAPTION: A sample calculation. Divides ~100 km~ by ~35 km/h~ and converts the result to minutes. This could be used to calculate how long (in minutes) it takes to go 100 kilometres at a speed of 35 km/h. - #+attr_latex: :height 250px - #+name: sample-results-expression - [[../screenshots/sample-conversion-results-expression-converter.png]] +1. Select the "Convert Unit Expressions" if it is not already selected. You should see a screen like in figure [[main-interface-expression]]: + #+CAPTION: Taken in version 0.3.0 + #+attr_latex: :height 250px + #+name: main-interface-expression + [[../screenshots/main-interface-expression-converter.png]] +2. Enter a [[*Unit Expressions][unit expression]] in the From box. This can be something like "~7 km~" or "~6 ft - 2 in~" or "~3 kg m + 9 lb ft + (35 mm)^2 * (85 oz) / (20 in)~". +3. Enter a unit name (or another unit expression) in the To box. +4. Press the Convert button. This will calculate the value of the first expression, and convert it to a multiple of the second unit (or expression). + #+CAPTION: A sample calculation. Divides ~100 km~ by ~35 km/h~ and converts the result to minutes. This could be used to calculate how long (in minutes) it takes to go 100 kilometres at a speed of 35 km/h. + #+attr_latex: :height 250px + #+name: sample-results-expression + [[../screenshots/sample-conversion-results-expression-converter.png]] * 7Units Settings - All settings can be accessed in the tab with the gear icon. - #+CAPTION: The settings menu, as of version 0.4.0 - #+ATTR_LaTeX: :height 250px - [[../screenshots/main-interface-settings.png]] +All settings can be accessed in the tab with the gear icon. +#+CAPTION: The settings menu, as of version 0.4.0 +#+ATTR_LaTeX: :height 250px +[[../screenshots/main-interface-settings.png]] ** Rounding Settings - These settings control how the output of a unit conversion is rounded. - - Fixed Precision :: Round to a fixed number of [[https://en.wikipedia.org/wiki/Significant_figures][significant digits]]. The number of significant digits is controlled by the precision slider below. - - Fixed Decimal Places :: Round to a fixed number of digits after the decimal point. The number of decimal places is also controlled by the precision slider below. - - Scientific Precision :: Intelligent rounding which uses the precision of the input value(s) to determine the output precision. Not affected by the precision slider. +These settings control how the output of a unit conversion is rounded. +- Fixed Precision :: Round to a fixed number of [[https://en.wikipedia.org/wiki/Significant_figures][significant digits]]. The number of significant digits is controlled by the precision slider below. +- Fixed Decimal Places :: Round to a fixed number of digits after the decimal point. The number of decimal places is also controlled by the precision slider below. +- Scientific Precision :: Intelligent rounding which uses the precision of the input value(s) to determine the output precision. Not affected by the precision slider. ** Prefix Repetition Settings - These settings control when you are allowed to repeat unit prefixes (e.g. kilokilometre) - - No Repetition :: Units may only have one prefix. - - No Restriction :: Units may have any number of prefixes. - - Complex Repetition :: A complex rule which makes it so that each power of 10 has one and only one prefix combination. Units may have the following prefixes: - - one of: centi, deci, deca, hecto - - one of: zepto, atto, femto, pico, nano, micro, milli, kilo, mega, giga, tera, peta, exa, zetta - - any number of yocto or yotta - - they must be in this order - - all prefixes must be of the same sign (either all magnifying or all reducing) +These settings control when you are allowed to repeat unit prefixes (e.g. kilokilometre) +- No Repetition :: Units may only have one prefix. +- No Restriction :: Units may have any number of prefixes. +- Complex Repetition :: A complex rule which makes it so that each power of 10 has one and only one prefix combination. Units may have the following prefixes: + - one of: centi, deci, deca, hecto + - one of: zepto, atto, femto, pico, nano, micro, milli, kilo, mega, giga, tera, peta, exa, zetta + - any number of yocto or yotta + - they must be in this order + - all prefixes must be of the same sign (either all magnifying or all reducing) ** Search Settings - These settings control which prefixes are shown in the "Convert Units" tab. Only coherent SI units (e.g. metre, second, newton, joule) will get prefixes. Some prefixed units are created in the unitfile, and will stay regardless of this setting (though they can be removed from the unitfile). - - Never Include Prefixed Units :: Prefixed units will only be shown if they are explicitly added to the unitfile. - - Include Common Prefixes :: Every coherent unit will have its kilo- and milli- versions included in the list. - - Include All Single Prefixes :: Every coherent unit will have every prefixed version of it included in the list. +These settings control which prefixes are shown in the "Convert Units" tab. Only coherent SI units (e.g. metre, second, newton, joule) will get prefixes. Some prefixed units are created in the unitfile, and will stay regardless of this setting (though they can be removed from the unitfile). +- Never Include Prefixed Units :: Prefixed units will only be shown if they are explicitly added to the unitfile. +- Include Common Prefixes :: Every coherent unit will have its kilo- and milli- versions included in the list. +- Include All Single Prefixes :: Every coherent unit will have every prefixed version of it included in the list. ** Miscellaneous Settings - - Convert One Way Only :: In the simple conversion tab, only imperial/customary units will be shown on the left, and only metric units[fn:1] will be shown on the right. Units listed in the exceptions file (~src/main/resources/metric_exceptions.txt~) will be shown on both sides. This is a way to reduce the number of options you must search through if you only convert one way. The expressions tab is unaffected. - - Show Duplicates in "Convert Units" :: If unchecked, any unit that has multiple names will only have one included in the Convert Units lists. The selected name will be the longest; if there are multiple longest names one is selected arbitrarily. You will still be able to use these alternate names in the expressions tab. +- Convert One Way Only :: In the simple conversion tab, only imperial/customary units will be shown on the left, and only metric units[fn:1] will be shown on the right. Units listed in the exceptions file (~src/main/resources/metric_exceptions.txt~) will be shown on both sides. This is a way to reduce the number of options you must search through if you only convert one way. The expressions tab is unaffected. +- Show Duplicates in "Convert Units" :: If unchecked, any unit that has multiple names will only have one included in the Convert Units lists. The selected name will be the longest; if there are multiple longest names one is selected arbitrarily. You will still be able to use these alternate names in the expressions tab. +- Use Default Datafiles :: If unchecked, the default units, prefixes and diension names will not be loaded (except for SI base units and their dimensions). +- Locale :: Which language is used for the interface of 7Units. Custom locales can be added. ** Configuration File The settings are saved in a configuration file. On Windows, this is located at \\ ~%USERPROFILE%/AppData/Local/SevenUnits/config.txt~. On other operating systems, this is located at ~$HOME/.config/SevenUnits/config.txt~. The directory containing the ~SevenUnits~ directory can be overridden with the environment variables ~$LOCALAPPDATA~ on Windows or ~$XDG_CONFIG_HOME~ elsewhere. @@ -87,19 +90,22 @@ The possible setting names are: - ~include_duplicates~ :: Whether duplicate units should be shown; can be either ~true~ or ~false~. - ~search_prefix_rule~ :: The prefix search rule; can be ~NO_PREFIXES~, ~COMMON_PREFIXES~, \\ or ~ALL_METRIC_PREFIXES~. +- ~use_default_datafiles~ :: Whether default datafiles should be used; can be either ~true~ or ~false~. +- ~locale~ :: The name of the locale to use. + This is the name of a locale file, excluding the extension, in one of the directories specified in the data specification. For example, if this is 'en', 7Units will look for a file called 'en.txt' in either ~src/main/resources/locales/~ or ~[CONFIG]/locales/~. You can also use the special setting names ~custom_unit_file~, ~custom_dimension_file~ and ~custom_exception_file~ to add custom units, dimensions and metric exceptions to the system. These files use the same format as the standard files. These setting names can be used more than once to include multiple unit, dimension or exception files. * Appendices ** Unit Expressions - A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence): - - Exponentiation (^); the exponent must be an integer. Both units and numbers can be raised to an exponent - - Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter"). - You can also divide with ~|~ to create fractions. Using ~|~ instead of ~/~ gives the division a higher precedence than any other operator. For example, "2|5^2" evaluates to 4/25, not 2/25. - - Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds. +A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence): +- Exponentiation (^); the exponent must be an integer. Both units and numbers can be raised to an exponent +- Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter"). + You can also divide with ~|~ to create fractions. Using ~|~ instead of ~/~ gives the division a higher precedence than any other operator. For example, "2|5^2" evaluates to 4/25, not 2/25. +- Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds. Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \deg C ** Other Expressions - There are also a simplified version of expressions for prefixes and dimensions. Only multiplication, division and exponentation are supported. Currently, exponentation is not supported for dimensions, but that may be fixed in the future. +There are also a simplified version of expressions for prefixes and dimensions. Only multiplication, division and exponentation are supported. Currently, exponentation is not supported for dimensions, but that may be fixed in the future. * Footnotes -[fn:1] 7Units's definition of "metric" is stricter than the SI, but all of the common units that are commonly considered metric but not included in 7Units's definition are included in the exceptions file. +[fn:1] 7Units's definition of "metric" is stricter than the SI, but all of the common units that are commonly considered metric but not included in 7Units's definition are included in the exceptions file. diff --git a/docs/manual.pdf b/docs/manual.pdf index 5fcc115..38d5c66 100644 Binary files a/docs/manual.pdf and b/docs/manual.pdf differ diff --git a/docs/manual.tex b/docs/manual.tex index e16198f..a1f7c63 100644 --- a/docs/manual.tex +++ b/docs/manual.tex @@ -1,4 +1,4 @@ -% Created 2024-03-24 Sun 13:16 +% Created 2025-06-01 Sun 20:01 % Intended LaTeX compiler: pdflatex \documentclass[11pt]{article} \usepackage[utf8]{inputenc} @@ -13,15 +13,15 @@ \usepackage{capt-of} \usepackage{hyperref} \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} -\date{2024 March 23} +\date{2025 June 1} \title{7Units User Manual\\\medskip -\large For Version 0.5.0} +\large For Version 1.0.0} \hypersetup{ pdfauthor={}, pdftitle={7Units User Manual}, pdfkeywords={}, pdfsubject={}, - pdfcreator={Emacs 29.2 (Org mode 9.6.15)}, + pdfcreator={Emacs 29.3 (Org mode 9.6.15)}, pdflang={English}} \begin{document} @@ -30,21 +30,22 @@ \newpage \section{Introduction and Purpose} -\label{sec:orgc09fcc7} +\label{sec:org6fb5a20} 7Units is a program that can be used to convert units. This document outlines how to use the program. \section{System Requirements} -\label{sec:orga902335} +\label{sec:org9d01e55} \begin{itemize} \item Works on all major operating systems \\[0pt] \textbf{NOTE:} All screenshots in this document were taken on Windows 10. If you use a different operating system, the program will probably look different than what is shown. \item Java version 11+ required +\item Gradle required +\item To run the software, simply run './gradlew run' in the main directory. \end{itemize} - \newpage \section{How to Use 7Units} -\label{sec:orgdec078f} +\label{sec:org1fd2398} \subsection{Simple Unit Conversion} -\label{sec:org785ebcb} +\label{sec:orga7e83a8} \begin{enumerate} \item Select the "Convert Units" tab if it is not already selected. You should see a screen like in figure \ref{main-interface-dimension}: \begin{figure}[htbp] @@ -69,7 +70,7 @@ \end{figure} \end{enumerate} \subsection{Complex Unit Conversion} -\label{sec:org75a0192} +\label{sec:orgf923c07} \begin{enumerate} \item Select the "Convert Unit Expressions" if it is not already selected. You should see a screen like in figure \ref{main-interface-expression}: \begin{figure}[htbp] @@ -77,7 +78,7 @@ \includegraphics[height=250px]{../screenshots/main-interface-expression-converter.png} \caption{\label{main-interface-expression}Taken in version 0.3.0} \end{figure} -\item Enter a \hyperref[sec:org3724d84]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}". +\item Enter a \hyperref[sec:orga2dae79]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}". \item Enter a unit name (or another unit expression) in the To box. \item Press the Convert button. This will calculate the value of the first expression, and convert it to a multiple of the second unit (or expression). \begin{figure}[htbp] @@ -87,7 +88,7 @@ \end{figure} \end{enumerate} \section{7Units Settings} -\label{sec:orgae2806f} +\label{sec:org69dc7d4} All settings can be accessed in the tab with the gear icon. \begin{figure}[htbp] \centering @@ -95,7 +96,7 @@ All settings can be accessed in the tab with the gear icon. \caption{The settings menu, as of version 0.4.0} \end{figure} \subsection{Rounding Settings} -\label{sec:org6d3e49c} +\label{sec:org8d63000} These settings control how the output of a unit conversion is rounded. \begin{description} \item[{Fixed Precision}] Round to a fixed number of \href{https://en.wikipedia.org/wiki/Significant\_figures}{significant digits}. The number of significant digits is controlled by the precision slider below. @@ -103,7 +104,7 @@ These settings control how the output of a unit conversion is rounded. \item[{Scientific Precision}] Intelligent rounding which uses the precision of the input value(s) to determine the output precision. Not affected by the precision slider. \end{description} \subsection{Prefix Repetition Settings} -\label{sec:org9aa98f8} +\label{sec:org1f1263f} These settings control when you are allowed to repeat unit prefixes (e.g. kilokilometre) \begin{description} \item[{No Repetition}] Units may only have one prefix. @@ -118,7 +119,7 @@ These settings control when you are allowed to repeat unit prefixes (e.g. kiloki \end{itemize} \end{description} \subsection{Search Settings} -\label{sec:org2745ba0} +\label{sec:org03df615} These settings control which prefixes are shown in the "Convert Units" tab. Only coherent SI units (e.g. metre, second, newton, joule) will get prefixes. Some prefixed units are created in the unitfile, and will stay regardless of this setting (though they can be removed from the unitfile). \begin{description} \item[{Never Include Prefixed Units}] Prefixed units will only be shown if they are explicitly added to the unitfile. @@ -126,13 +127,15 @@ These settings control which prefixes are shown in the "Convert Units" tab. Onl \item[{Include All Single Prefixes}] Every coherent unit will have every prefixed version of it included in the list. \end{description} \subsection{Miscellaneous Settings} -\label{sec:orgeabb2df} +\label{sec:org159a151} \begin{description} \item[{Convert One Way Only}] In the simple conversion tab, only imperial/customary units will be shown on the left, and only metric units\footnote{7Units's definition of "metric" is stricter than the SI, but all of the common units that are commonly considered metric but not included in 7Units's definition are included in the exceptions file.} will be shown on the right. Units listed in the exceptions file (\texttt{src/main/resources/metric\_exceptions.txt}) will be shown on both sides. This is a way to reduce the number of options you must search through if you only convert one way. The expressions tab is unaffected. \item[{Show Duplicates in "Convert Units"}] If unchecked, any unit that has multiple names will only have one included in the Convert Units lists. The selected name will be the longest; if there are multiple longest names one is selected arbitrarily. You will still be able to use these alternate names in the expressions tab. +\item[{Use Default Datafiles}] If unchecked, the default units, prefixes and diension names will not be loaded (except for SI base units and their dimensions). +\item[{Locale}] Which language is used for the interface of 7Units. Custom locales can be added. \end{description} \subsection{Configuration File} -\label{sec:org4cc2874} +\label{sec:org398eb26} The settings are saved in a configuration file. On Windows, this is located at \\[0pt] \texttt{\%USERPROFILE\%/AppData/Local/SevenUnits/config.txt}. On other operating systems, this is located at \texttt{\$HOME/.config/SevenUnits/config.txt}. The directory containing the \texttt{SevenUnits} directory can be overridden with the environment variables \texttt{\$LOCALAPPDATA} on Windows or \texttt{\$XDG\_CONFIG\_HOME} elsewhere. @@ -147,23 +150,26 @@ or \texttt{COMPLEX\_REPETITION}. \item[{\texttt{include\_duplicates}}] Whether duplicate units should be shown; can be either \texttt{true} or \texttt{false}. \item[{\texttt{search\_prefix\_rule}}] The prefix search rule; can be \texttt{NO\_PREFIXES}, \texttt{COMMON\_PREFIXES}, \\[0pt] or \texttt{ALL\_METRIC\_PREFIXES}. +\item[{\texttt{use\_default\_datafiles}}] Whether default datafiles should be used; can be either \texttt{true} or \texttt{false}. +\item[{\texttt{locale}}] The name of the locale to use. +This is the name of a locale file, excluding the extension, in one of the directories specified in the data specification. For example, if this is 'en', 7Units will look for a file called 'en.txt' in either \texttt{src/main/resources/locales/} or \texttt{[CONFIG]/locales/}. \end{description} You can also use the special setting names \texttt{custom\_unit\_file}, \texttt{custom\_dimension\_file} and \texttt{custom\_exception\_file} to add custom units, dimensions and metric exceptions to the system. These files use the same format as the standard files. These setting names can be used more than once to include multiple unit, dimension or exception files. \section{Appendices} -\label{sec:org60385a7} +\label{sec:org89d72bb} \subsection{Unit Expressions} -\label{sec:org3724d84} +\label{sec:orga2dae79} A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence): \begin{itemize} \item Exponentiation (\^{}); the exponent must be an integer. Both units and numbers can be raised to an exponent \item Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter"). You can also divide with \texttt{|} to create fractions. Using \texttt{|} instead of \texttt{/} gives the division a higher precedence than any other operator. For example, "2|5\textsuperscript{2}" evaluates to 4/25, not 2/25. \item Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds. -\end{itemize} Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \textdegree{} C +\end{itemize} \subsection{Other Expressions} -\label{sec:orgc72a672} +\label{sec:orgf67cbc9} There are also a simplified version of expressions for prefixes and dimensions. Only multiplication, division and exponentation are supported. Currently, exponentation is not supported for dimensions, but that may be fixed in the future. \end{document} diff --git a/docs/roadmap.org b/docs/roadmap.org deleted file mode 100644 index fdd12ac..0000000 --- a/docs/roadmap.org +++ /dev/null @@ -1,6 +0,0 @@ -* Version 1.0.0 Roadmap -Here is a list of the unfinished requirements for version 1.0.0. When everything here is met, I intend to release version 1.0.0 and consider 7Units complete (for the most part). - -These requirements are subject to change. I intend to finish version 1.0.0 by [2025-06-01 Sun]. - -Once the documentation is up to date, 7Units 1.0.0 can be released. -- cgit v1.2.3 From 25f972d198e50ad5a54fa175ec39887f02c33fdc Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 4 Jun 2025 18:39:03 -0500 Subject: Remove most comment warnings In some cases I've used @SuppressWarnings, which Gradle doesn't seem to respect, but I've solved all the other ones. --- src/main/java/sevenUnits/unit/BaseUnit.java | 7 +- src/main/java/sevenUnits/unit/BritishImperial.java | 8 ++ src/main/java/sevenUnits/unit/FunctionalUnit.java | 1 + src/main/java/sevenUnits/unit/LinearUnit.java | 6 +- src/main/java/sevenUnits/unit/LinearUnitValue.java | 94 ++++++++------ .../java/sevenUnits/unit/LoadingException.java | 59 +++++++-- src/main/java/sevenUnits/unit/Metric.java | 3 + src/main/java/sevenUnits/unit/USCustomary.java | 3 + src/main/java/sevenUnits/unit/Unit.java | 4 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 16 ++- src/main/java/sevenUnits/unit/UnitPrefix.java | 5 + src/main/java/sevenUnits/unit/UnitType.java | 7 +- src/main/java/sevenUnits/unit/UnitValue.java | 11 +- src/main/java/sevenUnits/utils/NameSymbol.java | 4 + src/main/java/sevenUnits/utils/ObjectProduct.java | 6 + .../java/sevenUnits/utils/UncertainDouble.java | 144 ++++++++++++++------- .../sevenUnitsGUI/DefaultPrefixRepetitionRule.java | 2 + src/main/java/sevenUnitsGUI/GridBagBuilder.java | 7 + src/main/java/sevenUnitsGUI/Presenter.java | 2 + .../java/sevenUnitsGUI/StandardDisplayRules.java | 2 + src/main/java/sevenUnitsGUI/ViewBot.java | 30 ++++- 21 files changed, 307 insertions(+), 114 deletions(-) diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java index fe85a7b..4781be9 100644 --- a/src/main/java/sevenUnits/unit/BaseUnit.java +++ b/src/main/java/sevenUnits/unit/BaseUnit.java @@ -50,9 +50,10 @@ public final class BaseUnit extends Unit { /** * Gets a base unit from the dimension it measures, its name and its symbol. * - * @param dimension dimension measured by this unit - * @param name name of unit - * @param symbol symbol of unit + * @param dimension dimension measured by this unit + * @param name name of unit + * @param symbol symbol of unit + * @param otherNames other possible names of unit * @return base unit * @since 2019-10-21 */ diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index 69a3c05..16255da 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -24,6 +24,9 @@ import sevenUnits.utils.NameSymbol; * @author Adrien Hopkins * @since 2019-10-21 */ +// this class is just constants, most of which are obvious from the variable name +// so no need to check for missing values +@SuppressWarnings("javadoc") public final class BritishImperial { /** * Imperial units that measure area @@ -55,11 +58,16 @@ public final class BritishImperial { public static final LinearUnit FOOT = YARD.dividedBy(3); public static final LinearUnit INCH = FOOT.dividedBy(12); public static final LinearUnit THOU = INCH.dividedBy(1000); + /** A chain, equal to 22 yards. */ public static final LinearUnit CHAIN = YARD.times(22); + /** A furlong, equal to 10 chains or 220 yards. */ public static final LinearUnit FURLONG = CHAIN.times(10); + /** A mile, equal to 8 furlongs or 1760 yards. */ public static final LinearUnit MILE = FURLONG.times(8); + /** A league, equal to 3 miles. */ public static final LinearUnit LEAGUE = MILE.times(3); + /** A nautical mile, around 1 arcminute around the Earth's circumference. */ 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); diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index 6de446f..8ca87b3 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -74,6 +74,7 @@ final class FunctionalUnit extends Unit { * base and returns that value expressed in this unit. * @param converterTo function that accepts a value expressed in the unit * and returns that value expressed in the unit's base. + * @param ns name and symbol of resulting unit * @throws NullPointerException if any argument is null * @since 2019-05-22 */ diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index d453a43..99bea96 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -66,6 +66,7 @@ public final class LinearUnit extends Unit { } /** + * @param unit unit to get base version of * @return the base unit associated with {@code unit}, as a * {@code LinearUnit}. * @since 2020-10-02 @@ -232,6 +233,7 @@ public final class LinearUnit extends Unit { } /** + * @param other unit to test equality with * @return true iff this unit and other are equal, * ignoring small differences caused by floating-point error. * @@ -397,7 +399,9 @@ public final class LinearUnit extends Unit { /** * Returns this unit to an exponent, rounding the resulting dimensions to the * nearest integer. - * + * + * @param exponent exponent to raise unit to + * @return result of rounded exponentation * @since 2024-08-22 * @see ObjectProduct#toExponentRounded */ diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index 678c59c..e4cc820 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -36,8 +36,9 @@ import sevenUnits.utils.UncertainDouble; * @since 2020-07-26 */ public final class LinearUnitValue { + /** The value 1 as a LinearUnitValue. */ public static final LinearUnitValue ONE = getExact(Metric.ONE, 1); - + /** * Gets an exact {@code LinearUnitValue} * @@ -52,12 +53,12 @@ public final class LinearUnitValue { Objects.requireNonNull(unit, "unit must not be null"), UncertainDouble.of(value, 0)); } - + /** * Gets an uncertain {@code LinearUnitValue} * - * @param unit unit to express with - * @param value value to express + * @param unit unit to express with + * @param value value to express * @return uncertain {@code LinearUnitValue} instance * @since 2020-07-26 */ @@ -67,11 +68,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 @@ -81,7 +82,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. @@ -90,7 +91,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}. @@ -99,7 +100,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 @@ -111,12 +112,13 @@ public final class LinearUnitValue { public final LinearUnitValue convertTo(final LinearUnit other) { return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); } - + /** * Convert a LinearUnitValue to a sum of multiple units, where all but the * last have exact integer values. * * @param others units to convert to + * @return terms of the sum * @throws IllegalArgumentException if no units are provided or units * provided have incompatible bases * @since 2024-08-15 @@ -130,7 +132,7 @@ public final class LinearUnitValue { throw new IllegalArgumentException( "All provided units must have the same base as the value."); } - + LinearUnitValue remaining = this; final List values = new ArrayList<>(others.size()); for (final LinearUnit unit : others.subList(0, others.size() - 1)) { @@ -140,13 +142,13 @@ public final class LinearUnitValue { values.add(value); remaining = remaining.minus(value); } - + final LinearUnitValue lastValue = remaining .convertTo(others.get(others.size() - 1)); values.add(lastValue); return values; } - + /** * Divides this value by a scalar * @@ -157,7 +159,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 * @@ -169,7 +171,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 @@ -187,14 +189,17 @@ 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 * km) returns true. - *

    - * If avoidFPErrors is true, this method will attempt to avoid floating-point - * errors, at the cost of not always being transitive. + * + * @param obj object to test equality with + * @param avoidFPErrors if true, this method will attempt to avoid + * floating-point errors, at the cost of not always + * being transitive. + * @return true iff this and obj are equal * * @since 2020-07-28 */ @@ -208,7 +213,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 @@ -222,10 +227,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 @@ -233,7 +238,7 @@ public final class LinearUnitValue { public final LinearUnit getUnit() { return this.unit; } - + /** * @return the value * @since 2020-09-29 @@ -241,7 +246,7 @@ public final class LinearUnitValue { public final UncertainDouble getValue() { return this.value; } - + /** * @return the exact value * @since 2020-09-07 @@ -249,13 +254,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 @@ -268,17 +273,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 * @@ -290,17 +295,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 * @@ -311,7 +316,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 * @@ -323,7 +328,7 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.times(multiplier.unit), this.value.times(multiplier.value)); } - + /** * Raises a value to an exponent * @@ -335,9 +340,12 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.toExponent(exponent), this.value.toExponentExact(exponent)); } - + /** * Raises this value to an exponent, rounding all dimensions to integers. + * + * @param exponent exponent to raise this value to + * @return result of exponentation * * @since 2024-08-22 * @see ObjectProduct#toExponentRounded @@ -346,12 +354,12 @@ public final class LinearUnitValue { return LinearUnitValue.of(this.unit.toExponentRounded(exponent), this.value.toExponentExact(exponent)); } - + @Override public String toString() { return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN); } - + /** * Returns a string representing the object.
    * If the attached unit has a name or symbol, the string looks like "12 km". @@ -362,6 +370,10 @@ public final class LinearUnitValue { *

    * Non-exact values are rounded intelligently based on their uncertainty. * + * @param showUncertainty whether to show the value's uncertainty + * @param roundingMode how to round numbers in this string + * @return string representing this value + * * @since 2020-07-26 */ public String toString(final boolean showUncertainty, @@ -369,9 +381,9 @@ public final class LinearUnitValue { final Optional primaryName = this.unit.getPrimaryName(); final Optional 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 ? "(" : "") @@ -380,7 +392,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/LoadingException.java b/src/main/java/sevenUnits/unit/LoadingException.java index 9376ed7..18630a4 100644 --- a/src/main/java/sevenUnits/unit/LoadingException.java +++ b/src/main/java/sevenUnits/unit/LoadingException.java @@ -27,20 +27,29 @@ import java.util.Optional; * @since 2024-08-22 */ public final class LoadingException extends RuntimeException { + /** The type of file that was being loaded. */ public static enum FileType { - UNIT, DIMENSION + @SuppressWarnings("javadoc") UNIT, @SuppressWarnings("javadoc") DIMENSION } - + private static final long serialVersionUID = -8167971828216907607L; - + private final long lineNumber; private final String line; private final Optional file; - + private final FileType fileType; - + private final RuntimeException problem; - + + /** + * Create a LoadingException from some information, without a file. + * + * @param lineNumber line number error happened on + * @param line text of invalid line + * @param fileType type of file + * @param problem problem, as an Exception + */ public LoadingException(long lineNumber, String line, FileType fileType, RuntimeException problem) { super(problem); @@ -50,7 +59,16 @@ public final class LoadingException extends RuntimeException { this.fileType = fileType; this.problem = problem; } - + + /** + * Create a LoadingException from some information, with a file. + * + * @param lineNumber line number error happened on + * @param line text of invalid line + * @param file file error happened on + * @param fileType type of file + * @param problem problem, as an Exception + */ public LoadingException(long lineNumber, String line, Path file, FileType fileType, RuntimeException problem) { super(problem); @@ -60,15 +78,21 @@ public final class LoadingException extends RuntimeException { this.fileType = fileType; this.problem = problem; } - + + /** + * @return the file this error happened in, if there is one + */ public Optional file() { return this.file; } - + + /** + * @return type of file that this error happened in + */ public FileType fileType() { return this.fileType; } - + @Override public String getMessage() { return this.file @@ -81,15 +105,24 @@ public final class LoadingException extends RuntimeException { this.lineNumber, this.fileType.toString().toLowerCase(), this.line, this.problem)); } - + + /** + * @return text of line that caused this error + */ public String line() { return this.line; } - + + /** + * @return number of line that caused this error + */ public long lineNumber() { return this.lineNumber; } - + + /** + * @return the error, as an exception + */ public RuntimeException problem() { return this.problem; } diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 2fc3928..16859bf 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -37,6 +37,9 @@ import sevenUnits.utils.ObjectProduct; * @author Adrien Hopkins * @since 2019-10-16 */ +// this class is just constants, most of which are obvious from the variable name +// so no need to check for missing values +@SuppressWarnings("javadoc") public final class Metric { /// dimensions used by SI units // base dimensions, as BaseDimensions diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java index fce829e..fa3f26e 100644 --- a/src/main/java/sevenUnits/unit/USCustomary.java +++ b/src/main/java/sevenUnits/unit/USCustomary.java @@ -22,6 +22,9 @@ package sevenUnits.unit; * @author Adrien Hopkins * @since 2019-10-21 */ +// this class is just constants, most of which are obvious from the variable name +// so no need to check for missing values +@SuppressWarnings("javadoc") public final class USCustomary { /** * US Customary units that measure area diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index d25b362..61a5dbf 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -262,7 +262,7 @@ public abstract class Unit implements Nameable { } /** - * Returns true iff this unit is metric. + * Determines whether this unit is metric. *

    * "Metric" is defined by three conditions: *

      @@ -278,6 +278,8 @@ public abstract class Unit implements Nameable { * All SI units (as designated by the BIPM) except the degree Celsius are * considered "metric" by this definition. * + * @return true iff this unit is metric. + * * @since 2020-08-27 */ public final boolean isMetric() { diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 444b366..95e953f 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -212,6 +212,8 @@ public final class UnitDatabase { * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. * + * @param map map to base iterator on + * * @since 2019-04-14 * @since v0.2.0 */ @@ -527,6 +529,8 @@ public final class UnitDatabase { * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. * + * @param map map to base itorator on + * * @since 2019-04-14 * @since v0.2.0 */ @@ -1529,6 +1533,9 @@ public final class UnitDatabase { /** * Returns true iff there is a unit set with this name. + * + * @param name name to check for + * @return true iff there is a unit set with this name * * @since 2024-08-16 */ @@ -1574,7 +1581,7 @@ public final class UnitDatabase { // the previous operation breaks negative numbers, fix them! // (i.e. -2 becomes - 2) - // FIXME the previous operaton also breaks stuff like "1e-5" + // FIXME the previous operation also breaks stuff like "1e-5" for (int i = 0; i < modifiedExpression.length(); i++) { if (modifiedExpression.charAt(i) == '-' && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^') @@ -1620,6 +1627,7 @@ public final class UnitDatabase { *
    * * @param expression expression to parse + * @return parsed unit dimension * @throws IllegalArgumentException if the expression cannot be parsed * @throws NullPointerException if expression is null * @since 2019-04-13 @@ -1848,6 +1856,7 @@ public final class UnitDatabase { * This method only works with linear units. * * @param expression expression to parse + * @return parsed unit * @throws IllegalArgumentException if the expression cannot be parsed * @throws NullPointerException if expression is null * @since 2019-01-07 @@ -1890,6 +1899,9 @@ public final class UnitDatabase { /** * Get a unit set from its name, throwing a {@link NoSuchElementException} if * there is none. + * + * @param name name of unit set + * @return unit set with that name * * @since 2024-08-16 */ @@ -1977,6 +1989,7 @@ public final class UnitDatabase { * {@link #loadDimensionFile}. * * @param stream stream to load from + * @return list of all errors that happened loading the stream * @since 2021-03-27 */ public List loadDimensionsFromStream( @@ -2047,6 +2060,7 @@ public final class UnitDatabase { * {@link #loadUnitsFile}. * * @param stream stream to load from + * @return list of all errors that happened loading the stream * @since 2021-03-27 */ public List loadUnitsFromStream(InputStream stream) { diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index ec4be48..be4d8fb 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -124,6 +124,7 @@ public final class UnitPrefix implements Nameable { } /** + * @param other prefix to compare to * @return true iff this prefix and other are equal, * ignoring small differences caused by floating-point error. * @@ -175,6 +176,8 @@ public final class UnitPrefix implements Nameable { /** * Adds {@code other} to this prefix and returns the result. + * @param other prefix to add + * @return sum of prefixes * * @since 2024-03-03 */ @@ -184,6 +187,8 @@ public final class UnitPrefix implements Nameable { /** * Subtracts {@code other} from this prefix and returns the result. + * @param other prefix to subtract + * @return difference of prefixes * * @since 2024-03-03 */ diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java index 9a87288..6c0f7a9 100644 --- a/src/main/java/sevenUnits/unit/UnitType.java +++ b/src/main/java/sevenUnits/unit/UnitType.java @@ -32,7 +32,12 @@ import java.util.function.Predicate; * @since 2022-04-10 */ public enum UnitType { - METRIC, SEMI_METRIC, NON_METRIC; + /** Units that pass {@link Unit#isMetric} */ + METRIC, + /** certain exceptions like the degree Celsius */ + SEMI_METRIC, + /** Non-metric, non-excepted units */ + NON_METRIC; /** * Determines which type a unit is. The type will be: diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index 9b485e3..aee836e 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -56,6 +56,7 @@ public final class UnitValue { } /** + * @param other unit to try to convert to * @return true if this value can be converted to {@code other}. * @since 2020-10-01 */ @@ -76,11 +77,12 @@ public final class UnitValue { } /** - * Returns this unit value represented as a {@code LinearUnitValue} with this + * Returns this unit value represented as a {@link LinearUnitValue} with this * unit's base unit as the base. * * @param ns name and symbol for the base unit, use NameSymbol.EMPTY if not * needed. + * @return this unit as a {@link LinearUnitValue} * @since 2020-09-29 */ public final LinearUnitValue convertToBase(NameSymbol ns) { @@ -89,13 +91,14 @@ public final class UnitValue { } /** + * @param newUnit unit to use for this value * @return a {@code LinearUnitValue} that is equivalent to this value. It * will have zero uncertainty. * @since 2020-09-29 */ - public final LinearUnitValue convertToLinear(LinearUnit other) { - return LinearUnitValue.getExact(other, - this.getUnit().convertTo(other, this.getValue())); + public final LinearUnitValue convertToLinear(LinearUnit newUnit) { + return LinearUnitValue.getExact(newUnit, + this.getUnit().convertTo(newUnit, this.getValue())); } /** diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 290dcd6..a91009e 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -31,6 +31,7 @@ import java.util.Set; * @since 2019-10-21 */ public final class NameSymbol { + /** The {@code NameSymbol} with all fields empty. */ public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), Optional.empty(), new HashSet<>()); @@ -297,6 +298,9 @@ public final class NameSymbol { * Creates and returns a copy of this {@code NameSymbol} with the provided * extra name. If this {@code NameSymbol} has a primary name, the provided * name will become an other name, otherwise it will become the primary name. + * + * @param name additional name to add + * @return copy of this NameSymbol with the additional name * * @since v0.4.0 * @since 2022-04-19 diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index d403bdc..3861095 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -31,6 +31,7 @@ import java.util.function.Function; * objects can be multiplied and exponentiated. * * @author Adrien Hopkins + * @param type of object that is being multiplied * @since 2019-10-16 */ public class ObjectProduct implements Nameable { @@ -69,6 +70,7 @@ public class ObjectProduct implements Nameable { * else. * * @param object object that will be in the product + * @param type of object contained in returned ObjectProduct * @return product * @since 2019-10-16 * @throws NullPointerException if object is null @@ -267,6 +269,9 @@ public class ObjectProduct implements Nameable { * * This function will send a warning (via standard error) if the rounding * significantly changes the value. + * + * @param exponent exponent to raise this product to + * @return result of exponentiation * * @since 2024-08-22 */ @@ -338,6 +343,7 @@ public class ObjectProduct implements Nameable { } /** + * @param nameSymbol name to add to this product * @return named version of this {@code ObjectProduct}, using data from * {@code nameSymbol} * @since 2021-12-15 diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index ca94817..c70574a 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -35,21 +35,24 @@ public final class UncertainDouble implements Comparable { * 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 + ")?"); - + /** * 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. * + * @param s string to parse + * @return parsed {@code UncertainDouble} + * * @throws NumberFormatException if the argument is not a number * * @since 2022-04-18 @@ -59,10 +62,11 @@ public final class UncertainDouble implements Comparable { 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, RoundingMode)} - * and returns the corresponding {@code UncertainDouble} instance. + * Parses a string in the form of + * {@link UncertainDouble#toString(boolean, RoundingMode)} and returns the + * corresponding {@code UncertainDouble} instance. *

    * This method allows some alternative forms of the string representation, * such as using "+-" instead of "±". @@ -75,11 +79,11 @@ public final class UncertainDouble implements Comparable { 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 string \"" + s + "\"."); - + double value, uncertainty; try { value = Double.parseDouble(matcher.group(1)); @@ -87,7 +91,7 @@ public final class UncertainDouble implements Comparable { throw new IllegalArgumentException( "String " + s + " not in correct format."); } - + final String uncertaintyString = matcher.group(2); if (uncertaintyString == null) { uncertainty = 0; @@ -99,35 +103,45 @@ public final class UncertainDouble implements Comparable { "String " + s + " not in correct format."); } } - + return UncertainDouble.of(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and absolute * uncertainty. * + * @param value double's value + * @param uncertainty double's uncertainty (non-negative) + * @return {@code UncertainDouble} instance with these parameters + * * @since 2020-09-07 */ public static final UncertainDouble of(double value, double uncertainty) { return new UncertainDouble(value, uncertainty); } - + /** * Gets an {@code UncertainDouble} from its value and relative * uncertainty. * + * @param value double's value + * @param relativeUncertainty double's uncertainty (non-negative); the + * absolute uncertainty is equal to this value + * multiplied by {@code relativeUncertainty} + * @return {@code UncertainDouble} instance with these parameters + * * @since 2020-09-07 */ public static final UncertainDouble ofRelative(double value, double relativeUncertainty) { return new UncertainDouble(value, value * relativeUncertainty); } - + private final double value; - + private final double uncertainty; - + /** * @param value * @param uncertainty @@ -138,7 +152,7 @@ public final class UncertainDouble implements Comparable { // uncertainty should only ever be positive this.uncertainty = Math.abs(uncertainty); } - + /** * Compares this {@code UncertainDouble} with another * {@code UncertainDouble}. @@ -156,10 +170,13 @@ public final class UncertainDouble implements Comparable { public final int compareTo(UncertainDouble o) { return Double.compare(this.value, o.value); } - + /** * Returns the quotient of {@code this} and {@code other}. * + * @param other number to divide by + * @return quotient + * * @since 2020-09-07 */ public final UncertainDouble dividedBy(UncertainDouble other) { @@ -167,16 +184,19 @@ public final class UncertainDouble implements Comparable { 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}. * + * @param other number to divide by + * @return quotient + * * @since 2020-09-07 */ 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 +210,7 @@ public final class UncertainDouble implements Comparable { return false; return true; } - + /** * @param other another {@code UncertainDouble} * @return true iff this and {@code other} are within each other's @@ -202,7 +222,7 @@ public final class UncertainDouble implements Comparable { 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 +237,19 @@ public final class UncertainDouble implements Comparable { // 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 +258,7 @@ public final class UncertainDouble implements Comparable { result = prime * result + Double.hashCode(this.uncertainty); return result; } - + /** * @return true iff the value has no uncertainty * @@ -247,10 +267,13 @@ public final class UncertainDouble implements Comparable { public final boolean isExact() { return this.uncertainty == 0; } - + /** * Returns the difference of {@code this} and {@code other}. * + * @param other number to subtract + * @return result of subtraction + * * @since 2020-09-07 */ public final UncertainDouble minus(UncertainDouble other) { @@ -258,19 +281,25 @@ public final class UncertainDouble implements Comparable { 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}. * + * @param other number to subtract + * @return result of subtraction + * * @since 2020-09-07 */ public final UncertainDouble minusExact(double other) { return UncertainDouble.of(this.value - other, this.uncertainty); } - + /** * Returns the sum of {@code this} and {@code other}. * + * @param other number to add + * @return result of addition + * * @since 2020-09-07 */ public final UncertainDouble plus(UncertainDouble other) { @@ -278,16 +307,19 @@ public final class UncertainDouble implements Comparable { 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}. * + * @param other number to add + * @return result of addition + * * @since 2020-09-07 */ public final UncertainDouble plusExact(double other) { return UncertainDouble.of(this.value + other, this.uncertainty); } - + /** * @return relative uncertainty * @since 2020-09-07 @@ -295,10 +327,13 @@ public final class UncertainDouble implements Comparable { public final double relativeUncertainty() { return this.uncertainty / this.value; } - + /** * Returns the product of {@code this} and {@code other}. * + * @param other number to multiply + * @return product + * * @since 2020-09-07 */ public final UncertainDouble times(UncertainDouble other) { @@ -306,49 +341,58 @@ public final class UncertainDouble implements Comparable { 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}. * + * @param other number to multiply + * @return product + * * @since 2020-09-07 */ 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}. * + * @param other exponent + * @return result of exponentation + * * @since 2020-09-07 */ 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}. * + * @param other exponent + * @return result of exponentation + * * @since 2020-09-07 */ public final UncertainDouble toExponentExact(double other) { return UncertainDouble.ofRelative(Math.pow(this.value, other), this.relativeUncertainty() * other); } - + /** * Returns a string representation of this {@code UncertainDouble}. *

    - * This method returns the same value as {@link #toString(boolean, RoundingMode)}, - * but {@code showUncertainty} is true if and only if the uncertainty is - * non-zero. + * This method returns the same value as + * {@link #toString(boolean, RoundingMode)}, but {@code showUncertainty} is + * true if and only if the uncertainty is non-zero. * *

    * Examples: @@ -365,7 +409,7 @@ public final class UncertainDouble implements Comparable { public final String toString() { return this.toString(!this.isExact(), RoundingMode.HALF_EVEN); } - + /** * Returns a string representation of this {@code UncertainDouble}. *

    @@ -392,36 +436,40 @@ public final class UncertainDouble implements Comparable { * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 ± 0.0" * * + * @param showUncertainty uncertainty is only shown if this parameter is true + * @param roundingMode how to round values + * @return string representation of this {@code UncertainDouble} + * * @since 2020-09-07 */ 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 absolute uncertainty * @since 2020-09-07 @@ -429,7 +477,7 @@ public final class UncertainDouble implements Comparable { 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 1fb2709..ccc9f1c 100644 --- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java +++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java @@ -15,12 +15,14 @@ import sevenUnits.unit.UnitPrefix; * @since 2020-08-26 */ public enum DefaultPrefixRepetitionRule implements Predicate> { + /** Prefix repetition is never allowed; only one prefix may be used. */ NO_REPETITION { @Override public boolean test(List prefixes) { return prefixes.size() <= 1; } }, + /** Prefix repetition is always allowed, without restrictions. */ NO_RESTRICTION { @Override public boolean test(List prefixes) { diff --git a/src/main/java/sevenUnitsGUI/GridBagBuilder.java b/src/main/java/sevenUnitsGUI/GridBagBuilder.java index fdbaee7..95fc094 100644 --- a/src/main/java/sevenUnitsGUI/GridBagBuilder.java +++ b/src/main/java/sevenUnitsGUI/GridBagBuilder.java @@ -418,6 +418,7 @@ final class GridBagBuilder { /** * @param anchor anchor to set + * @return this, so that you can chain methods * @since 2018-11-30 * @since v0.1.0 */ @@ -428,6 +429,7 @@ final class GridBagBuilder { /** * @param fill fill to set + * @return this, so that you can chain methods * @since 2018-11-30 * @since v0.1.0 */ @@ -438,6 +440,7 @@ final class GridBagBuilder { /** * @param insets insets to set + * @return this, so that you can chain methods * @since 2018-11-30 * @since v0.1.0 */ @@ -448,6 +451,7 @@ final class GridBagBuilder { /** * @param ipadx ipadx to set + * @return this, so that you can chain methods * @since 2018-11-30 * @since v0.1.0 */ @@ -458,6 +462,7 @@ final class GridBagBuilder { /** * @param ipady ipady to set + * @return this, so that you can chain methods * @since 2018-11-30 * @since v0.1.0 */ @@ -468,6 +473,7 @@ final class GridBagBuilder { /** * @param weightx weightx to set + * @return this, so that you can chain methods * @since 2018-11-30 * @since v0.1.0 */ @@ -478,6 +484,7 @@ final class GridBagBuilder { /** * @param weighty weighty to set + * @return this, so that you can chain methods * @since 2018-11-30 * @since v0.1.0 */ diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 44261a5..6dce39d 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -1280,6 +1280,8 @@ public final class Presenter { /** * Sets whether or not the default datafiles will be loaded. * This method automatically updates the view's units. + * + * @param useDefaultDatafiles whether or not default datafiles should be loaded */ public void setUseDefaultDatafiles(boolean useDefaultDatafiles) { this.useDefaultDatafiles = useDefaultDatafiles; diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java index d00263b..ff9c64e 100644 --- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java +++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java @@ -40,6 +40,7 @@ public final class StandardDisplayRules { */ public static final class FixedDecimals implements Function { + /** Regular expression used for converting this to a string. */ public static final Pattern TO_STRING_PATTERN = Pattern .compile("Round to (\\d+) decimal places"); /** @@ -101,6 +102,7 @@ public final class StandardDisplayRules { */ public static final class FixedPrecision implements Function { + /** Regular expression used for converting this to a string. */ public static final Pattern TO_STRING_PATTERN = Pattern .compile("Round to (\\d+) significant figures"); diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index aea30bb..60a8d7b 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -79,10 +79,12 @@ public final class ViewBot return Objects.hash(this.multiplierString, this.nameSymbol); } + /** @return A string representation of the prefix multiplier. */ public String multiplierString() { return this.multiplierString; } + /** @return A {@code NameSymbol} describing the prefix. */ public NameSymbol nameSymbol() { return this.nameSymbol; } @@ -112,6 +114,10 @@ public final class ViewBot private final UnitType unitType; /** + * @param nameSymbol name(s) and symbol of unit + * @param definition unit's definition string + * @param dimensionName name of unit's dimension + * @param unitType type of unit (metric/semi-metric/non-metric) * @since 2022-04-16 */ public UnitViewingRecord(NameSymbol nameSymbol, String definition, @@ -166,6 +172,7 @@ public final class ViewBot this.nameSymbol, this.unitType); } + /** @return name(s) and symbol of unit */ public NameSymbol nameSymbol() { return this.nameSymbol; } @@ -395,6 +402,10 @@ public final class ViewBot this.selectedDimensionName = selectedDimensionName; } + /** + * Sets the view's selected dimension + * @param selectedDimensionName name of dimension to select (string) + */ public void setSelectedDimensionName(String selectedDimensionName) { this.setSelectedDimensionName(Optional.of(selectedDimensionName)); } @@ -412,7 +423,7 @@ public final class ViewBot } /** - * @param toSelection the toSelection to set + * @param toSelection unit set in the 'To' selection * @since 2022-01-29 */ public void setToSelection(Optional toSelection) { @@ -420,6 +431,9 @@ public final class ViewBot "toSelection cannot be null."); } + /** + * @param toSelection unit set in the 'To' selection + */ public void setToSelection(String toSelection) { this.setToSelection(Optional.of(toSelection)); } @@ -439,18 +453,32 @@ public final class ViewBot // do nothing, ViewBot supports selecting any unit } + /** + * @param viewedPrefixName name of prefix being used + */ public void setViewedPrefixName(Optional viewedPrefixName) { this.prefixViewerSelection = viewedPrefixName; } + /** + * @param viewedPrefixName name of prefix being used (may not be null) + * @throws NullPointerException if {@code viewedPrefixName} is null + */ public void setViewedPrefixName(String viewedPrefixName) { this.setViewedPrefixName(Optional.of(viewedPrefixName)); } + /** + * @param viewedUnitName name of unit being used + */ public void setViewedUnitName(Optional viewedUnitName) { this.unitViewerSelection = viewedUnitName; } + /** + * @param viewedUnitName name of unit being used (may not be null) + * @throws NullPointerException if {@code viewedUnitName} is null + */ public void setViewedUnitName(String viewedUnitName) { this.setViewedUnitName(Optional.of(viewedUnitName)); } -- cgit v1.2.3 From d80b80857e739eb32afd7625789944abd3afe376 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 4 Jun 2025 18:58:15 -0500 Subject: Update copyright notices' years I used the Git history for years after 2019, and only included 2019 or 2018 if they were already there. I also added copyright notices to all code files that don't already have them. --- src/main/java/sevenUnits/ProgramInfo.java | 2 +- src/main/java/sevenUnits/package-info.java | 2 +- src/main/java/sevenUnits/unit/BaseDimension.java | 2 +- src/main/java/sevenUnits/unit/BaseUnit.java | 2 +- src/main/java/sevenUnits/unit/BritishImperial.java | 2 +- src/main/java/sevenUnits/unit/FunctionalUnit.java | 2 +- src/main/java/sevenUnits/unit/LinearUnit.java | 2 +- src/main/java/sevenUnits/unit/LinearUnitValue.java | 2 +- src/main/java/sevenUnits/unit/LoadingException.java | 2 +- src/main/java/sevenUnits/unit/Metric.java | 2 +- src/main/java/sevenUnits/unit/USCustomary.java | 2 +- src/main/java/sevenUnits/unit/Unit.java | 2 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 2 +- src/main/java/sevenUnits/unit/UnitPrefix.java | 2 +- src/main/java/sevenUnits/unit/UnitType.java | 2 +- src/main/java/sevenUnits/unit/UnitValue.java | 2 +- src/main/java/sevenUnits/unit/package-info.java | 2 +- .../utils/ConditionalExistenceCollections.java | 2 +- src/main/java/sevenUnits/utils/DecimalComparison.java | 2 +- src/main/java/sevenUnits/utils/ExpressionParser.java | 2 +- src/main/java/sevenUnits/utils/NameSymbol.java | 2 +- src/main/java/sevenUnits/utils/Nameable.java | 2 +- src/main/java/sevenUnits/utils/ObjectProduct.java | 2 +- .../java/sevenUnits/utils/SemanticVersionNumber.java | 2 +- src/main/java/sevenUnits/utils/UncertainDouble.java | 2 +- src/main/java/sevenUnits/utils/package-info.java | 2 +- .../java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java | 15 ++++++++++++++- src/main/java/sevenUnitsGUI/DelegateListModel.java | 2 +- .../java/sevenUnitsGUI/ExpressionConversionView.java | 2 +- src/main/java/sevenUnitsGUI/FilterComparator.java | 2 +- src/main/java/sevenUnitsGUI/GridBagBuilder.java | 2 +- src/main/java/sevenUnitsGUI/Main.java | 2 +- src/main/java/sevenUnitsGUI/PrefixSearchRule.java | 2 +- src/main/java/sevenUnitsGUI/Presenter.java | 2 +- src/main/java/sevenUnitsGUI/SearchBoxList.java | 2 +- src/main/java/sevenUnitsGUI/StandardDisplayRules.java | 2 +- src/main/java/sevenUnitsGUI/TabbedView.java | 2 +- src/main/java/sevenUnitsGUI/UnitConversionRecord.java | 2 +- src/main/java/sevenUnitsGUI/UnitConversionView.java | 2 +- src/main/java/sevenUnitsGUI/View.java | 2 +- src/main/java/sevenUnitsGUI/ViewBot.java | 2 +- src/main/java/sevenUnitsGUI/package-info.java | 2 +- src/test/java/sevenUnits/unit/UnitDatabaseTest.java | 2 +- src/test/java/sevenUnits/unit/UnitTest.java | 2 +- src/test/java/sevenUnits/unit/UnitValueTest.java | 16 ++++++++++++++++ .../utils/ConditionalExistenceCollectionsTest.java | 2 +- src/test/java/sevenUnits/utils/ExpressionParserTest.java | 2 +- src/test/java/sevenUnits/utils/NameSymbolTest.java | 16 ++++++++++++++++ src/test/java/sevenUnits/utils/ObjectProductTest.java | 2 +- src/test/java/sevenUnits/utils/SemanticVersionTest.java | 2 +- src/test/java/sevenUnits/utils/UncertainDoubleTest.java | 2 +- src/test/java/sevenUnitsGUI/I18nTest.java | 16 ++++++++++++++++ src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java | 2 +- src/test/java/sevenUnitsGUI/PrefixSearchTest.java | 2 +- src/test/java/sevenUnitsGUI/PresenterTest.java | 2 +- src/test/java/sevenUnitsGUI/RoundingTest.java | 2 +- src/test/java/sevenUnitsGUI/TabbedViewTest.java | 2 +- 57 files changed, 115 insertions(+), 54 deletions(-) diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index 4fd4375..dda5d2c 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-2024 Adrien Hopkins + * Copyright (C) 2021-2025 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 diff --git a/src/main/java/sevenUnits/package-info.java b/src/main/java/sevenUnits/package-info.java index 33b98fc..b90a5ea 100644 --- a/src/main/java/sevenUnits/package-info.java +++ b/src/main/java/sevenUnits/package-info.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019-2021 Adrien Hopkins + * Copyright (C) 2019-2025 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 diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java index 3f1f75f..cfa6af9 100644 --- a/src/main/java/sevenUnits/unit/BaseDimension.java +++ b/src/main/java/sevenUnits/unit/BaseDimension.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019, 2022 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java index 4781be9..0d375f8 100644 --- a/src/main/java/sevenUnits/unit/BaseUnit.java +++ b/src/main/java/sevenUnits/unit/BaseUnit.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index 16255da..e327055 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index 8ca87b3..fb24629 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 99bea96..6c1aba3 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index e4cc820..8d30907 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/LoadingException.java b/src/main/java/sevenUnits/unit/LoadingException.java index 18630a4..b806147 100644 --- a/src/main/java/sevenUnits/unit/LoadingException.java +++ b/src/main/java/sevenUnits/unit/LoadingException.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2024 Adrien Hopkins + * Copyright (C) 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 16859bf..166cbcf 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java index fa3f26e..1923d9e 100644 --- a/src/main/java/sevenUnits/unit/USCustomary.java +++ b/src/main/java/sevenUnits/unit/USCustomary.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 61a5dbf..5d67ed0 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 95e953f..6cf794b 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018-2024 Adrien Hopkins + * Copyright (C) 2018-2025 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 diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index be4d8fb..6ac9128 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java index 6c0f7a9..d445418 100644 --- a/src/main/java/sevenUnits/unit/UnitType.java +++ b/src/main/java/sevenUnits/unit/UnitType.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index aee836e..97b7e54 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/unit/package-info.java b/src/main/java/sevenUnits/unit/package-info.java index 6aedb9d..6d867d3 100644 --- a/src/main/java/sevenUnits/unit/package-info.java +++ b/src/main/java/sevenUnits/unit/package-info.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019-2025 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 diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index cb3b8ce..b46e821 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index 03dd15b..4136818 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 4f9cfa8..8ab6c95 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019, 2024 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index a91009e..c0c8f94 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index 3959a64..15026b7 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2020 Adrien Hopkins + * Copyright (C) 2020, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 3861095..772ff5e 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2021, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index 937d474..bf198ae 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index c70574a..46803d1 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2020 Adrien Hopkins + * Copyright (C) 2020-2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnits/utils/package-info.java b/src/main/java/sevenUnits/utils/package-info.java index 350c62d..6cae117 100644 --- a/src/main/java/sevenUnits/utils/package-info.java +++ b/src/main/java/sevenUnits/utils/package-info.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018-2020 Adrien Hopkins + * Copyright (C) 2018-2025 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 diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java index ccc9f1c..fbf78a3 100644 --- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java +++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java @@ -1,5 +1,18 @@ /** - * @since 2020-08-26 + * Copyright (C) 2020, 2022, 2024, 2025 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package sevenUnitsGUI; diff --git a/src/main/java/sevenUnitsGUI/DelegateListModel.java b/src/main/java/sevenUnitsGUI/DelegateListModel.java index 798383b..4925197 100644 --- a/src/main/java/sevenUnitsGUI/DelegateListModel.java +++ b/src/main/java/sevenUnitsGUI/DelegateListModel.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2022, 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 diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java index 882c995..ead07c5 100644 --- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java +++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Adrien Hopkins + * Copyright (C) 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/FilterComparator.java b/src/main/java/sevenUnitsGUI/FilterComparator.java index 484a98f..d7a59c4 100644 --- a/src/main/java/sevenUnitsGUI/FilterComparator.java +++ b/src/main/java/sevenUnitsGUI/FilterComparator.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/GridBagBuilder.java b/src/main/java/sevenUnitsGUI/GridBagBuilder.java index 95fc094..81d1e79 100644 --- a/src/main/java/sevenUnitsGUI/GridBagBuilder.java +++ b/src/main/java/sevenUnitsGUI/GridBagBuilder.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/Main.java b/src/main/java/sevenUnitsGUI/Main.java index ff61b3b..b573a09 100644 --- a/src/main/java/sevenUnitsGUI/Main.java +++ b/src/main/java/sevenUnitsGUI/Main.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java index 69f09e6..1937fd2 100644 --- a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java +++ b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 6dce39d..7c66d55 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-2022 Adrien Hopkins + * Copyright (C) 2021-2025 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 diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java index 8fba459..1748083 100644 --- a/src/main/java/sevenUnitsGUI/SearchBoxList.java +++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java index ff9c64e..a19b680 100644 --- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java +++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 9850aac..97b93dc 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java index da1a9d2..6dde230 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index e3fb28f..a6cc399 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-2022 Adrien Hopkins + * Copyright (C) 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index 4140992..f934bb5 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-2022 Adrien Hopkins + * Copyright (C) 2021, 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index 60a8d7b..8e24e64 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/main/java/sevenUnitsGUI/package-info.java b/src/main/java/sevenUnitsGUI/package-info.java index cff1ded..c0ccc1b 100644 --- a/src/main/java/sevenUnitsGUI/package-info.java +++ b/src/main/java/sevenUnitsGUI/package-info.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Adrien Hopkins + * Copyright (C) 2021-2025 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 diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index e55d6af..56296b4 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index f8d3040..4d9a103 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2021, 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnits/unit/UnitValueTest.java b/src/test/java/sevenUnits/unit/UnitValueTest.java index 69569ae..6b80986 100644 --- a/src/test/java/sevenUnits/unit/UnitValueTest.java +++ b/src/test/java/sevenUnits/unit/UnitValueTest.java @@ -1,3 +1,19 @@ +/** + * Copyright (C) 2021, 2025 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package sevenUnits.unit; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java index 868385b..29675de 100644 --- a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java +++ b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java index 15701ce..463880b 100644 --- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java +++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 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 diff --git a/src/test/java/sevenUnits/utils/NameSymbolTest.java b/src/test/java/sevenUnits/utils/NameSymbolTest.java index 327cd9f..55e09c4 100644 --- a/src/test/java/sevenUnits/utils/NameSymbolTest.java +++ b/src/test/java/sevenUnits/utils/NameSymbolTest.java @@ -1,3 +1,19 @@ +/** + * Copyright (C) 2025 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package sevenUnits.utils; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/sevenUnits/utils/ObjectProductTest.java b/src/test/java/sevenUnits/utils/ObjectProductTest.java index 8c6b353..584b3f3 100644 --- a/src/test/java/sevenUnits/utils/ObjectProductTest.java +++ b/src/test/java/sevenUnits/utils/ObjectProductTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018 Adrien Hopkins + * Copyright (C) 2018, 2021, 2024, 2025 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 diff --git a/src/test/java/sevenUnits/utils/SemanticVersionTest.java b/src/test/java/sevenUnits/utils/SemanticVersionTest.java index 1e59ae3..295ad39 100644 --- a/src/test/java/sevenUnits/utils/SemanticVersionTest.java +++ b/src/test/java/sevenUnits/utils/SemanticVersionTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java index b251b31..fe66e2d 100644 --- a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java +++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Adrien Hopkins + * Copyright (C) 2021, 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnitsGUI/I18nTest.java b/src/test/java/sevenUnitsGUI/I18nTest.java index 73bd727..2875db6 100644 --- a/src/test/java/sevenUnitsGUI/I18nTest.java +++ b/src/test/java/sevenUnitsGUI/I18nTest.java @@ -1,3 +1,19 @@ +/** + * Copyright (C) 2025 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java index 476e407..ce75cca 100644 --- a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java +++ b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java index 8790315..c7c652b 100644 --- a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java +++ b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 1d9b45b..20d0c8a 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022-2025 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 diff --git a/src/test/java/sevenUnitsGUI/RoundingTest.java b/src/test/java/sevenUnitsGUI/RoundingTest.java index f749f85..535167c 100644 --- a/src/test/java/sevenUnitsGUI/RoundingTest.java +++ b/src/test/java/sevenUnitsGUI/RoundingTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 diff --git a/src/test/java/sevenUnitsGUI/TabbedViewTest.java b/src/test/java/sevenUnitsGUI/TabbedViewTest.java index 017e9ea..7819452 100644 --- a/src/test/java/sevenUnitsGUI/TabbedViewTest.java +++ b/src/test/java/sevenUnitsGUI/TabbedViewTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Adrien Hopkins + * Copyright (C) 2022, 2024, 2025 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 -- cgit v1.2.3 From 79e1653caf5c30667877a158433cbcd766a135af Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 4 Jun 2025 19:45:37 -0500 Subject: Add version numbers to all @since tags Specifically, for every @since tag with a date, I added another that contains the correspending version. I did not add date @since tags to comments that do not have them, as that would be too tedious for what it's worth. These dates could still be found by using git bisect though. --- src/main/java/sevenUnits/ProgramInfo.java | 2 +- src/main/java/sevenUnits/unit/BaseDimension.java | 3 ++ src/main/java/sevenUnits/unit/BaseUnit.java | 6 +++ src/main/java/sevenUnits/unit/BritishImperial.java | 5 +++ src/main/java/sevenUnits/unit/FunctionalUnit.java | 5 +++ src/main/java/sevenUnits/unit/LinearUnit.java | 14 +++++++ src/main/java/sevenUnits/unit/LinearUnitValue.java | 23 +++++++++++ .../java/sevenUnits/unit/LoadingException.java | 1 + src/main/java/sevenUnits/unit/Metric.java | 2 + src/main/java/sevenUnits/unit/USCustomary.java | 5 +++ src/main/java/sevenUnits/unit/Unit.java | 14 +++++++ src/main/java/sevenUnits/unit/UnitDatabase.java | 24 +++++++++++ src/main/java/sevenUnits/unit/UnitPrefix.java | 11 +++++ src/main/java/sevenUnits/unit/UnitType.java | 2 + src/main/java/sevenUnits/unit/UnitValue.java | 6 +++ .../utils/ConditionalExistenceCollections.java | 14 +++++++ .../java/sevenUnits/utils/DecimalComparison.java | 1 + .../java/sevenUnits/utils/ExpressionParser.java | 2 + src/main/java/sevenUnits/utils/NameSymbol.java | 14 ++++++- src/main/java/sevenUnits/utils/Nameable.java | 7 ++++ src/main/java/sevenUnits/utils/ObjectProduct.java | 15 +++++++ .../sevenUnits/utils/SemanticVersionNumber.java | 48 +++++++++++----------- .../java/sevenUnits/utils/UncertainDouble.java | 24 +++++++++++ .../sevenUnitsGUI/DefaultPrefixRepetitionRule.java | 1 + src/main/java/sevenUnitsGUI/DelegateListModel.java | 1 + .../sevenUnitsGUI/ExpressionConversionView.java | 8 ++-- src/main/java/sevenUnitsGUI/Main.java | 4 +- src/main/java/sevenUnitsGUI/PrefixSearchRule.java | 12 +++--- src/main/java/sevenUnitsGUI/Presenter.java | 45 ++++++++++++++++++++ src/main/java/sevenUnitsGUI/SearchBoxList.java | 5 +++ .../java/sevenUnitsGUI/StandardDisplayRules.java | 20 +++++---- src/main/java/sevenUnitsGUI/TabbedView.java | 20 +++++---- .../java/sevenUnitsGUI/UnitConversionRecord.java | 17 ++++---- .../java/sevenUnitsGUI/UnitConversionView.java | 24 +++++------ src/main/java/sevenUnitsGUI/View.java | 20 ++++----- src/main/java/sevenUnitsGUI/ViewBot.java | 26 +++++++++++- src/main/java/sevenUnitsGUI/package-info.java | 1 + .../java/sevenUnits/unit/UnitDatabaseTest.java | 15 +++++++ src/test/java/sevenUnits/unit/UnitValueTest.java | 10 +++++ .../utils/ConditionalExistenceCollectionsTest.java | 3 ++ .../sevenUnits/utils/ExpressionParserTest.java | 1 + src/test/java/sevenUnits/utils/NameSymbolTest.java | 5 +++ .../java/sevenUnits/utils/SemanticVersionTest.java | 10 +++++ .../java/sevenUnits/utils/UncertainDoubleTest.java | 2 + src/test/java/sevenUnitsGUI/I18nTest.java | 7 +++- .../java/sevenUnitsGUI/PrefixRepetitionTest.java | 10 ++--- src/test/java/sevenUnitsGUI/PrefixSearchTest.java | 16 ++++---- src/test/java/sevenUnitsGUI/PresenterTest.java | 26 ++++++------ src/test/java/sevenUnitsGUI/RoundingTest.java | 23 ++++++----- src/test/java/sevenUnitsGUI/TabbedViewTest.java | 8 ++-- 50 files changed, 460 insertions(+), 128 deletions(-) diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index dda5d2c..9b9832e 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -21,8 +21,8 @@ import sevenUnits.utils.SemanticVersionNumber; /** * Information about 7Units * - * @since 0.3.1 * @since 2021-06-28 + * @since v0.3.1 */ public final class ProgramInfo { diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java index cfa6af9..fe7b772 100644 --- a/src/main/java/sevenUnits/unit/BaseDimension.java +++ b/src/main/java/sevenUnits/unit/BaseDimension.java @@ -26,6 +26,7 @@ import sevenUnits.utils.Nameable; * * @author Adrien Hopkins * @since 2019-10-16 + * @since v0.3.0 */ public final class BaseDimension implements Nameable { /** @@ -35,6 +36,7 @@ public final class BaseDimension implements Nameable { * @param symbol symbol used for dimension * @return dimension * @since 2019-10-16 + * @since v0.3.0 */ public static BaseDimension valueOf(final String name, final String symbol) { return new BaseDimension(name, symbol); @@ -57,6 +59,7 @@ public final class BaseDimension implements Nameable { * @param symbol symbol of unit * @throws NullPointerException if any argument is null * @since 2019-10-16 + * @since v0.3.0 */ private BaseDimension(final String name, final String symbol) { this.name = Objects.requireNonNull(name, "name must not be null."); diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java index 0d375f8..2898de5 100644 --- a/src/main/java/sevenUnits/unit/BaseUnit.java +++ b/src/main/java/sevenUnits/unit/BaseUnit.java @@ -31,6 +31,7 @@ import sevenUnits.utils.NameSymbol; * * @author Adrien Hopkins * @since 2019-10-16 + * @since v0.3.0 */ public final class BaseUnit extends Unit { /** @@ -41,6 +42,7 @@ public final class BaseUnit extends Unit { * @param symbol symbol of unit * @return base unit * @since 2019-10-16 + * @since v0.3.0 */ public static BaseUnit valueOf(final BaseDimension dimension, final String name, final String symbol) { @@ -56,6 +58,7 @@ public final class BaseUnit extends Unit { * @param otherNames other possible names of unit * @return base unit * @since 2019-10-21 + * @since v0.3.0 */ public static BaseUnit valueOf(final BaseDimension dimension, final String name, final String symbol, final Set otherNames) { @@ -75,6 +78,7 @@ public final class BaseUnit extends Unit { * @param symbol symbol of unit * @throws NullPointerException if any argument is null * @since 2019-10-16 + * @since v0.3.0 */ private BaseUnit(final BaseDimension dimension, final String primaryName, final String symbol, final Set otherNames) { @@ -90,6 +94,7 @@ public final class BaseUnit extends Unit { * * @return this unit as a {@code LinearUnit} * @since 2019-10-16 + * @since v0.3.0 */ public LinearUnit asLinearUnit() { return LinearUnit.valueOf(this.getBase(), 1); @@ -108,6 +113,7 @@ public final class BaseUnit extends Unit { /** * @return dimension * @since 2019-10-16 + * @since v0.3.0 */ public final BaseDimension getBaseDimension() { return this.dimension; diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index e327055..a6fd43f 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -23,6 +23,7 @@ import sevenUnits.utils.NameSymbol; * * @author Adrien Hopkins * @since 2019-10-21 + * @since v0.3.0 */ // this class is just constants, most of which are obvious from the variable name // so no need to check for missing values @@ -33,6 +34,7 @@ public final class BritishImperial { * * @author Adrien Hopkins * @since 2019-11-08 + * @since v0.3.0 */ public static final class Area { public static final LinearUnit SQUARE_FOOT = Length.FOOT.toExponent(2); @@ -48,6 +50,7 @@ public final class BritishImperial { * * @author Adrien Hopkins * @since 2019-10-28 + * @since v0.3.0 */ public static final class Length { /** @@ -81,6 +84,7 @@ public final class BritishImperial { * * @author Adrien Hopkins * @since 2019-11-08 + * @since v0.3.0 */ public static final class Mass { public static final LinearUnit POUND = Metric.GRAM.times(453.59237); @@ -99,6 +103,7 @@ public final class BritishImperial { * * @author Adrien Hopkins * @since 2019-11-08 + * @since v0.3.0 */ public static final class Volume { public static final LinearUnit FLUID_OUNCE = Metric.LITRE diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index fb24629..41db164 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -27,6 +27,7 @@ import sevenUnits.utils.ObjectProduct; * * @author Adrien Hopkins * @since 2019-05-22 + * @since v0.3.0 */ final class FunctionalUnit extends Unit { /** @@ -34,6 +35,7 @@ final class FunctionalUnit extends Unit { * that value expressed in this unit. * * @since 2019-05-22 + * @since v0.3.0 */ private final DoubleUnaryOperator converterFrom; @@ -42,6 +44,7 @@ final class FunctionalUnit extends Unit { * value expressed in the unit's base. * * @since 2019-05-22 + * @since v0.3.0 */ private final DoubleUnaryOperator converterTo; @@ -55,6 +58,7 @@ final class FunctionalUnit extends Unit { * and returns that value expressed in the unit's base. * @throws NullPointerException if any argument is null * @since 2019-05-22 + * @since v0.3.0 */ public FunctionalUnit(final ObjectProduct base, final DoubleUnaryOperator converterFrom, @@ -77,6 +81,7 @@ final class FunctionalUnit extends Unit { * @param ns name and symbol of resulting unit * @throws NullPointerException if any argument is null * @since 2019-05-22 + * @since v0.3.0 */ public FunctionalUnit(final ObjectProduct base, final DoubleUnaryOperator converterFrom, diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 6c1aba3..7191196 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -29,6 +29,7 @@ import sevenUnits.utils.UncertainDouble; * * @author Adrien Hopkins * @since 2019-10-16 + * @since v0.3.0 */ public final class LinearUnit extends Unit { /** @@ -39,6 +40,7 @@ public final class LinearUnit extends Unit { * @param value value to convert * @return value expressed as a {@code LinearUnit} * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if unit is null */ public static LinearUnit fromUnitValue(final Unit unit, final double value) { @@ -56,6 +58,7 @@ public final class LinearUnit extends Unit { * @param ns name(s) and symbol of unit * @return value expressed as a {@code LinearUnit} * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if unit or ns is null */ public static LinearUnit fromUnitValue(final Unit unit, final double value, @@ -70,6 +73,7 @@ public final class LinearUnit extends Unit { * @return the base unit associated with {@code unit}, as a * {@code LinearUnit}. * @since 2020-10-02 + * @since v0.3.0 */ public static LinearUnit getBase(final Unit unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); @@ -84,6 +88,7 @@ public final class LinearUnit extends Unit { * @param conversionFactor number to multiply base by * @return product of base and conversion factor * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if unitBase is null */ public static LinearUnit valueOf(final ObjectProduct unitBase, @@ -101,6 +106,7 @@ public final class LinearUnit extends Unit { * @param ns name(s) and symbol of unit * @return product of base and conversion factor * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if unitBase is null */ public static LinearUnit valueOf(final ObjectProduct unitBase, @@ -116,6 +122,7 @@ public final class LinearUnit extends Unit { * * * @since 2019-10-16 + * @since v0.3.0 */ private final double conversionFactor; @@ -125,6 +132,7 @@ public final class LinearUnit extends Unit { * @param unitBase base of linear unit * @param conversionFactor conversion factor between base and unit * @since 2019-10-16 + * @since v0.3.0 */ private LinearUnit(final ObjectProduct unitBase, final double conversionFactor, final NameSymbol ns) { @@ -150,6 +158,7 @@ public final class LinearUnit extends Unit { * @param value value to convert * @return converted value * @since 2019-09-07 + * @since v0.3.0 * @throws IllegalArgumentException if {@code other} is incompatible for * conversion with this unit (as tested by * {@link Unit#canConvertTo}). @@ -181,6 +190,7 @@ public final class LinearUnit extends Unit { * Converts an {@code UncertainDouble} to the base unit. * * @since 2020-09-07 + * @since v0.3.0 */ UncertainDouble convertToBase(final UncertainDouble value) { return value.timesExact(this.getConversionFactor()); @@ -249,6 +259,7 @@ public final class LinearUnit extends Unit { /** * @return conversion factor * @since 2019-10-16 + * @since v0.3.0 */ public double getConversionFactor() { return this.conversionFactor; @@ -270,6 +281,7 @@ public final class LinearUnit extends Unit { * is a {@code BaseUnit b} where * {@code b.asLinearUnit().equals(this)} returns {@code true}.) * @since 2019-10-16 + * @since v0.3.0 */ public boolean isBase() { return this.isCoherent() && this.getBase().isSingleObject(); @@ -278,6 +290,7 @@ public final class LinearUnit extends Unit { /** * @return whether this unit is coherent (i.e. has conversion factor 1) * @since 2019-10-16 + * @since v0.3.0 */ public boolean isCoherent() { return this.getConversionFactor() == 1; @@ -403,6 +416,7 @@ public final class LinearUnit extends Unit { * @param exponent exponent to raise unit to * @return result of rounded exponentation * @since 2024-08-22 + * @since v1.0.0 * @see ObjectProduct#toExponentRounded */ public LinearUnit toExponentRounded(final double exponent) { diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index 8d30907..86520d7 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -34,6 +34,7 @@ import sevenUnits.utils.UncertainDouble; * * @author Adrien Hopkins * @since 2020-07-26 + * @since v0.3.0 */ public final class LinearUnitValue { /** The value 1 as a LinearUnitValue. */ @@ -46,6 +47,7 @@ public final class LinearUnitValue { * @param value value to express * @return exact {@code LinearUnitValue} instance * @since 2020-07-26 + * @since v0.3.0 */ public static final LinearUnitValue getExact(final LinearUnit unit, final double value) { @@ -61,6 +63,7 @@ public final class LinearUnitValue { * @param value value to express * @return uncertain {@code LinearUnitValue} instance * @since 2020-07-26 + * @since v0.3.0 */ public static final LinearUnitValue of(final LinearUnit unit, final UncertainDouble value) { @@ -77,6 +80,7 @@ public final class LinearUnitValue { * @param unit unit to express as * @param value value to express * @since 2020-07-26 + * @since v0.3.0 */ private LinearUnitValue(final LinearUnit unit, final UncertainDouble value) { this.unit = unit; @@ -87,6 +91,7 @@ public final class LinearUnitValue { * @return this value as a {@code UnitValue}. All uncertainty information is * removed from the returned value. * @since 2020-08-04 + * @since v0.3.0 */ public final UnitValue asUnitValue() { return UnitValue.of(this.unit, this.value.value()); @@ -96,6 +101,7 @@ public final class LinearUnitValue { * @param other a {@code LinearUnit} * @return true iff this value can be represented with {@code other}. * @since 2020-07-26 + * @since v0.3.0 */ public final boolean canConvertTo(final LinearUnit other) { return this.unit.canConvertTo(other); @@ -108,6 +114,7 @@ public final class LinearUnitValue { * @param other new unit to express value in * @return value expressed in {@code other} * @since 2020-07-26 + * @since v0.3.0 */ public final LinearUnitValue convertTo(final LinearUnit other) { return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); @@ -122,6 +129,7 @@ public final class LinearUnitValue { * @throws IllegalArgumentException if no units are provided or units * provided have incompatible bases * @since 2024-08-15 + * @since v1.0.0 */ public final List convertToMultiple( final List others) { @@ -155,6 +163,7 @@ public final class LinearUnitValue { * @param divisor value to divide by * @return multiplied value * @since 2020-07-28 + * @since v0.3.0 */ public LinearUnitValue dividedBy(final double divisor) { return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor)); @@ -166,6 +175,7 @@ public final class LinearUnitValue { * @param divisor value to multiply by * @return quotient * @since 2020-07-28 + * @since v0.3.0 */ public LinearUnitValue dividedBy(final LinearUnitValue divisor) { return LinearUnitValue.of(this.unit.dividedBy(divisor.unit), @@ -178,6 +188,7 @@ public final class LinearUnitValue { * km) returns true. * * @since 2020-07-26 + * @since v0.3.0 * @see #equals(Object, boolean) */ @Override @@ -202,6 +213,7 @@ public final class LinearUnitValue { * @return true iff this and obj are equal * * @since 2020-07-28 + * @since v0.3.0 */ public boolean equals(final Object obj, final boolean avoidFPErrors) { if (!avoidFPErrors) @@ -219,6 +231,7 @@ public final class LinearUnitValue { * @return true iff this and other are within each other's uncertainty range * * @since 2020-07-26 + * @since v0.3.0 */ public boolean equivalent(final LinearUnitValue other) { if (other == null @@ -234,6 +247,7 @@ public final class LinearUnitValue { /** * @return the unit * @since 2020-09-29 + * @since v0.3.0 */ public final LinearUnit getUnit() { return this.unit; @@ -242,6 +256,7 @@ public final class LinearUnitValue { /** * @return the value * @since 2020-09-29 + * @since v0.3.0 */ public final UncertainDouble getValue() { return this.value; @@ -250,6 +265,7 @@ public final class LinearUnitValue { /** * @return the exact value * @since 2020-09-07 + * @since v0.3.0 */ public final double getValueExact() { return this.value.value(); @@ -270,6 +286,7 @@ public final class LinearUnitValue { * @throws IllegalArgumentException if {@code subtrahend} has a unit that is * not compatible for addition * @since 2020-07-26 + * @since v0.3.0 */ public LinearUnitValue minus(final LinearUnitValue subtrahend) { Objects.requireNonNull(subtrahend, "subtrahend may not be null"); @@ -292,6 +309,7 @@ public final class LinearUnitValue { * @throws IllegalArgumentException if {@code addend} has a unit that is not * compatible for addition * @since 2020-07-26 + * @since v0.3.0 */ public LinearUnitValue plus(final LinearUnitValue addend) { Objects.requireNonNull(addend, "addend may not be null"); @@ -312,6 +330,7 @@ public final class LinearUnitValue { * @param multiplier value to multiply by * @return multiplied value * @since 2020-07-28 + * @since v0.3.0 */ public LinearUnitValue times(final double multiplier) { return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier)); @@ -323,6 +342,7 @@ public final class LinearUnitValue { * @param multiplier value to multiply by * @return product * @since 2020-07-28 + * @since v0.3.0 */ public LinearUnitValue times(final LinearUnitValue multiplier) { return LinearUnitValue.of(this.unit.times(multiplier.unit), @@ -335,6 +355,7 @@ public final class LinearUnitValue { * @param exponent exponent to raise to * @return result of exponentiation * @since 2020-07-28 + * @since v0.3.0 */ public LinearUnitValue toExponent(final int exponent) { return LinearUnitValue.of(this.unit.toExponent(exponent), @@ -348,6 +369,7 @@ public final class LinearUnitValue { * @return result of exponentation * * @since 2024-08-22 + * @since v1.0.0 * @see ObjectProduct#toExponentRounded */ public LinearUnitValue toExponentRounded(final double exponent) { @@ -375,6 +397,7 @@ public final class LinearUnitValue { * @return string representing this value * * @since 2020-07-26 + * @since v0.3.0 */ public String toString(final boolean showUncertainty, RoundingMode roundingMode) { diff --git a/src/main/java/sevenUnits/unit/LoadingException.java b/src/main/java/sevenUnits/unit/LoadingException.java index b806147..7b3d708 100644 --- a/src/main/java/sevenUnits/unit/LoadingException.java +++ b/src/main/java/sevenUnits/unit/LoadingException.java @@ -25,6 +25,7 @@ import java.util.Optional; * * @author Adrien Hopkins * @since 2024-08-22 + * @since v1.0.0 */ public final class LoadingException extends RuntimeException { /** The type of file that was being loaded. */ diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 166cbcf..34fd0b8 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -36,6 +36,7 @@ import sevenUnits.utils.ObjectProduct; * * @author Adrien Hopkins * @since 2019-10-16 + * @since v0.3.0 */ // this class is just constants, most of which are obvious from the variable name // so no need to check for missing values @@ -109,6 +110,7 @@ public final class Metric { * * @author Adrien Hopkins * @since 2019-11-08 + * @since v0.3.0 */ public static final class Constants { public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND) diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java index 1923d9e..be8c5e2 100644 --- a/src/main/java/sevenUnits/unit/USCustomary.java +++ b/src/main/java/sevenUnits/unit/USCustomary.java @@ -21,6 +21,7 @@ package sevenUnits.unit; * * @author Adrien Hopkins * @since 2019-10-21 + * @since v0.3.0 */ // this class is just constants, most of which are obvious from the variable name // so no need to check for missing values @@ -31,6 +32,7 @@ public final class USCustomary { * * @author Adrien Hopkins * @since 2019-11-08 + * @since v0.3.0 */ public static final class Area { public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT @@ -49,6 +51,7 @@ public final class USCustomary { * * @author Adrien Hopkins * @since 2019-10-28 + * @since v0.3.0 */ public static final class Length { public static final LinearUnit FOOT = BritishImperial.Length.FOOT; @@ -79,6 +82,7 @@ public final class USCustomary { * * @author Adrien Hopkins * @since 2019-11-08 + * @since v0.3.0 */ public static final class Mass { public static final LinearUnit GRAIN = BritishImperial.Mass.GRAIN; @@ -99,6 +103,7 @@ public final class USCustomary { * * @author Adrien Hopkins * @since 2019-11-08 + * @since v0.3.0 */ public static final class Volume { public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3); diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 5d67ed0..d651fe2 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -31,6 +31,7 @@ import sevenUnits.utils.ObjectProduct; * * @author Adrien Hopkins * @since 2019-10-16 + * @since v0.3.0 */ public abstract class Unit implements Nameable { /** @@ -51,6 +52,7 @@ public abstract class Unit implements Nameable { * and returns that value expressed in the unit's base. * @return a unit that uses the provided functions to convert. * @since 2019-05-22 + * @since v0.3.0 * @throws NullPointerException if any argument is null */ public static final Unit fromConversionFunctions( @@ -79,6 +81,7 @@ public abstract class Unit implements Nameable { * @param ns names and symbol of unit * @return a unit that uses the provided functions to convert. * @since 2019-05-22 + * @since v0.3.0 * @throws NullPointerException if any argument is null */ public static final Unit fromConversionFunctions( @@ -92,6 +95,7 @@ public abstract class Unit implements Nameable { * The combination of units that this unit is based on. * * @since 2019-10-16 + * @since v0.3.0 */ private final ObjectProduct unitBase; @@ -99,6 +103,7 @@ public abstract class Unit implements Nameable { * This unit's name(s) and symbol * * @since 2020-09-07 + * @since v0.3.0 */ private final NameSymbol nameSymbol; @@ -106,6 +111,7 @@ public abstract class Unit implements Nameable { * Cache storing the result of getDimension() * * @since 2019-10-16 + * @since v0.3.0 */ private transient ObjectProduct dimension = null; @@ -113,6 +119,7 @@ public abstract class Unit implements Nameable { * A constructor that constructs {@code BaseUnit} instances. * * @since 2019-10-16 + * @since v0.3.0 */ Unit(final NameSymbol nameSymbol) { if (this instanceof BaseUnit) { @@ -128,6 +135,7 @@ public abstract class Unit implements Nameable { * @param unitBase base of unit * @param ns names and symbol of unit * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if unitBase or ns is null */ protected Unit(ObjectProduct unitBase, NameSymbol ns) { @@ -187,6 +195,7 @@ public abstract class Unit implements Nameable { * @param value value to convert * @return converted value * @since 2019-05-22 + * @since v0.3.0 * @throws IllegalArgumentException if {@code other} is incompatible for * conversion with this unit (as tested by * {@link Unit#canConvertTo}). @@ -255,6 +264,7 @@ public abstract class Unit implements Nameable { /** * @return the nameSymbol * @since 2020-09-07 + * @since v0.3.0 */ @Override public final NameSymbol getNameSymbol() { @@ -281,6 +291,7 @@ public abstract class Unit implements Nameable { * @return true iff this unit is metric. * * @since 2020-08-27 + * @since v0.3.0 */ public final boolean isMetric() { // first condition - check that it is a linear unit @@ -302,6 +313,7 @@ public abstract class Unit implements Nameable { /** * @return a string representing this unit's definition * @since 2022-03-10 + * @since v0.3.0 */ public String toDefinitionString() { if (!this.unitBase.getNameSymbol().isEmpty()) @@ -314,6 +326,7 @@ public abstract class Unit implements Nameable { /** * @return a string containing both this unit's name and its definition * @since 2022-03-10 + * @since v0.3.0 */ public final String toFullString() { return this.toString() + " (" + this.toDefinitionString() + ")"; @@ -333,6 +346,7 @@ public abstract class Unit implements Nameable { * @param ns name(s) and symbol to use * @return a copy of this unit with provided name(s) and symbol * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if ns is null */ public Unit withName(final NameSymbol ns) { diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 6cf794b..b0d026f 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -138,6 +138,7 @@ public final class UnitDatabase { /** * @since 2019-05-03 + * @since v0.3.0 */ @Override public boolean equals(final Object o) { @@ -160,6 +161,7 @@ public final class UnitDatabase { /** * @since 2019-05-03 + * @since v0.3.0 */ @Override public int hashCode() { @@ -181,6 +183,7 @@ public final class UnitDatabase { * value. * * @since 2019-05-03 + * @since v0.3.0 */ @Override public String toString() { @@ -307,6 +310,7 @@ public final class UnitDatabase { * @return the next element in the iterator, without iterating over * it * @since 2019-05-03 + * @since v0.3.0 */ private Entry peek() { if (!this.hasNext()) @@ -333,6 +337,7 @@ public final class UnitDatabase { * of the representation are unspecified and subject to change. * * @since 2019-05-03 + * @since v0.3.0 */ @Override public String toString() { @@ -622,6 +627,7 @@ public final class UnitDatabase { * @return the next element in the iterator, without iterating over * it * @since 2019-05-03 + * @since v0.3.0 */ private String peek() { if (!this.hasNext()) @@ -645,6 +651,7 @@ public final class UnitDatabase { * of the representation are unspecified and subject to change. * * @since 2019-05-03 + * @since v0.3.0 */ @Override public String toString() { @@ -1136,6 +1143,7 @@ public final class UnitDatabase { * @param exponentUnit exponent * @return result * @since 2020-08-04 + * @since v0.3.0 */ private static final LinearUnitValue exponentiateUnitValues( final LinearUnitValue base, final LinearUnitValue exponentValue) { @@ -1151,6 +1159,7 @@ public final class UnitDatabase { /** * @return true if entry represents a removable duplicate entry of map. * @since 2021-05-22 + * @since v0.3.0 */ static boolean isRemovableDuplicate(Map map, Entry entry) { @@ -1236,6 +1245,7 @@ public final class UnitDatabase { * A parser that can parse unit value expressions. * * @since 2020-08-04 + * @since v0.3.0 */ private final ExpressionParser unitValueExpressionParser = new ExpressionParser.Builder<>( this::getLinearUnitValue) @@ -1297,6 +1307,7 @@ public final class UnitDatabase { * @param prefixRepetitionRule the rule that determines when prefix * repetition is allowed * @since 2020-08-26 + * @since v0.3.0 */ public UnitDatabase(Predicate> prefixRepetitionRule) { this.prefixlessUnits = new HashMap<>(); @@ -1468,6 +1479,7 @@ public final class UnitDatabase { * @param name name of unit set * @param value unit set to add * @since 2024-08-16 + * @since v1.0.0 */ public void addUnitSet(String name, List value) { if (value.isEmpty()) @@ -1486,6 +1498,7 @@ public final class UnitDatabase { * Removes all units, unit sets, prefixes and dimensions from this database. * * @since 2022-02-26 + * @since v0.4.0 */ public void clear() { this.dimensions.clear(); @@ -1538,6 +1551,7 @@ public final class UnitDatabase { * @return true iff there is a unit set with this name * * @since 2024-08-16 + * @since v1.0.0 */ public boolean containsUnitSetName(String name) { return this.unitSets.containsKey(name); @@ -1559,6 +1573,7 @@ public final class UnitDatabase { * @param expression expression to parse * @return {@code LinearUnitValue} representing value of expression * @since 2020-08-04 + * @since v0.3.0 */ public LinearUnitValue evaluateUnitExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); @@ -1697,6 +1712,7 @@ public final class UnitDatabase { * @param name name of unit * @return {@code LinearUnitValue} instance * @since 2020-08-04 + * @since v0.3.0 */ LinearUnitValue getLinearUnitValue(final String name) { try { @@ -1735,6 +1751,7 @@ public final class UnitDatabase { * @param unitName name of unit * @return prefixes * @since 2020-08-26 + * @since v0.3.0 */ List getPrefixesFromName(final String unitName) { final List prefixes = new ArrayList<>(); @@ -1800,6 +1817,7 @@ public final class UnitDatabase { /** * @return the prefixRepetitionRule * @since 2020-08-26 + * @since v0.3.0 */ public final Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; @@ -1904,6 +1922,7 @@ public final class UnitDatabase { * @return unit set with that name * * @since 2024-08-16 + * @since v1.0.0 */ public List getUnitSet(String name) { final List unitSet = this.unitSets.get(name); @@ -1916,6 +1935,7 @@ public final class UnitDatabase { * Parses a semicolon-separated expression to get the unit set being used. * * @since 2024-08-22 + * @since v1.0.0 */ List getUnitSetFromExpression(String expression) { final String[] parts = expression.split(";"); @@ -1991,6 +2011,7 @@ public final class UnitDatabase { * @param stream stream to load from * @return list of all errors that happened loading the stream * @since 2021-03-27 + * @since v0.3.0 */ public List loadDimensionsFromStream( final InputStream stream) { @@ -2062,6 +2083,7 @@ public final class UnitDatabase { * @param stream stream to load from * @return list of all errors that happened loading the stream * @since 2021-03-27 + * @since v0.3.0 */ public List loadUnitsFromStream(InputStream stream) { final List errors = new ArrayList<>(); @@ -2098,6 +2120,7 @@ public final class UnitDatabase { /** * @param prefixRepetitionRule the prefixRepetitionRule to set * @since 2020-08-26 + * @since v0.3.0 */ public final void setPrefixRepetitionRule( Predicate> prefixRepetitionRule) { @@ -2169,6 +2192,7 @@ public final class UnitDatabase { /** * @return an unmodifiable map mapping names to unit sets * @since 2024-08-16 + * @since v1.0.0 */ public Map> unitSetMap() { return Collections.unmodifiableMap(this.unitSets); diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index 6ac9128..0fd3421 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -28,6 +28,7 @@ import sevenUnits.utils.Nameable; * * @author Adrien Hopkins * @since 2019-10-16 + * @since v0.3.0 */ public final class UnitPrefix implements Nameable { /** @@ -36,6 +37,7 @@ public final class UnitPrefix implements Nameable { * @param multiplier multiplier of prefix * @return prefix * @since 2019-10-16 + * @since v0.3.0 */ public static UnitPrefix valueOf(final double multiplier) { return new UnitPrefix(multiplier, NameSymbol.EMPTY); @@ -48,6 +50,7 @@ public final class UnitPrefix implements Nameable { * @param ns name(s) and symbol of prefix * @return prefix * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if ns is null */ public static UnitPrefix valueOf(final double multiplier, @@ -60,6 +63,7 @@ public final class UnitPrefix implements Nameable { * This prefix's name(s) and symbol. * * @since 2022-04-16 + * @since v0.4.0 */ private final NameSymbol nameSymbol; @@ -67,6 +71,7 @@ public final class UnitPrefix implements Nameable { * The number that this prefix multiplies units by * * @since 2019-10-16 + * @since v0.3.0 */ private final double multiplier; @@ -88,6 +93,7 @@ public final class UnitPrefix implements Nameable { * @param divisor number to divide by * @return quotient of prefix and scalar * @since 2019-10-16 + * @since v0.3.0 */ public UnitPrefix dividedBy(final double divisor) { return valueOf(this.getMultiplier() / divisor); @@ -143,6 +149,7 @@ public final class UnitPrefix implements Nameable { /** * @return prefix's multiplier * @since 2019-11-26 + * @since v0.3.0 */ public double getMultiplier() { return this.multiplier; @@ -169,6 +176,7 @@ public final class UnitPrefix implements Nameable { * @param multiplicand number to multiply by * @return product of prefix and scalar * @since 2019-10-16 + * @since v0.3.0 */ public UnitPrefix times(final double multiplicand) { return valueOf(this.getMultiplier() * multiplicand); @@ -180,6 +188,7 @@ public final class UnitPrefix implements Nameable { * @return sum of prefixes * * @since 2024-03-03 + * @since v0.5.0 */ public UnitPrefix plus(final UnitPrefix other) { return valueOf(this.getMultiplier() + other.getMultiplier()); @@ -191,6 +200,7 @@ public final class UnitPrefix implements Nameable { * @return difference of prefixes * * @since 2024-03-03 + * @since v0.5.0 */ public UnitPrefix minus(final UnitPrefix other) { return valueOf(this.getMultiplier() - other.getMultiplier()); @@ -239,6 +249,7 @@ public final class UnitPrefix implements Nameable { * @param ns name(s) and symbol to use * @return copy of this prefix with provided name(s) and symbol * @since 2019-11-26 + * @since v0.3.0 * @throws NullPointerException if ns is null */ public UnitPrefix withName(final NameSymbol ns) { diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java index d445418..a331d3d 100644 --- a/src/main/java/sevenUnits/unit/UnitType.java +++ b/src/main/java/sevenUnits/unit/UnitType.java @@ -30,6 +30,7 @@ import java.util.function.Predicate; * * * @since 2022-04-10 + * @since v0.4.0 */ public enum UnitType { /** Units that pass {@link Unit#isMetric} */ @@ -51,6 +52,7 @@ public enum UnitType { * @param isSemiMetric predicate to determine if a unit is semi-metric * @return type of unit * @since 2022-04-18 + * @since v0.4.0 */ public static final UnitType getType(Unit u, Predicate isSemiMetric) { if (isSemiMetric.test(u)) diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index 97b7e54..4003c17 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -29,6 +29,7 @@ import sevenUnits.utils.NameSymbol; * * @author Adrien Hopkins * @since 2020-07-26 + * @since v0.3.0 */ public final class UnitValue { /** @@ -59,6 +60,7 @@ public final class UnitValue { * @param other unit to try to convert to * @return true if this value can be converted to {@code other}. * @since 2020-10-01 + * @since v0.3.0 */ public final boolean canConvertTo(Unit other) { return this.unit.canConvertTo(other); @@ -84,6 +86,7 @@ public final class UnitValue { * needed. * @return this unit as a {@link LinearUnitValue} * @since 2020-09-29 + * @since v0.3.0 */ public final LinearUnitValue convertToBase(NameSymbol ns) { final LinearUnit base = LinearUnit.getBase(this.unit).withName(ns); @@ -95,6 +98,7 @@ public final class UnitValue { * @return a {@code LinearUnitValue} that is equivalent to this value. It * will have zero uncertainty. * @since 2020-09-29 + * @since v0.3.0 */ public final LinearUnitValue convertToLinear(LinearUnit newUnit) { return LinearUnitValue.getExact(newUnit, @@ -121,6 +125,7 @@ public final class UnitValue { /** * @return the unit * @since 2020-09-29 + * @since v0.3.0 */ public final Unit getUnit() { return this.unit; @@ -129,6 +134,7 @@ public final class UnitValue { /** * @return the value * @since 2020-09-29 + * @since v0.3.0 */ public final double getValue() { return this.value; diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index b46e821..dd21a22 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -53,6 +53,7 @@ import java.util.function.Predicate; * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 */ public final class ConditionalExistenceCollections { /** @@ -60,6 +61,7 @@ public final class ConditionalExistenceCollections { * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param type of element in collection */ static final class ConditionalExistenceCollection @@ -73,6 +75,7 @@ public final class ConditionalExistenceCollections { * @param collection * @param existenceCondition * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceCollection(final Collection collection, final Predicate existenceCondition) { @@ -149,6 +152,7 @@ public final class ConditionalExistenceCollections { * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param type of elements in iterator */ static final class ConditionalExistenceIterator implements Iterator { @@ -163,6 +167,7 @@ public final class ConditionalExistenceCollections { * @param iterator * @param condition * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceIterator(final Iterator iterator, final Predicate condition) { @@ -175,6 +180,7 @@ public final class ConditionalExistenceCollections { * Gets the next element, and sets nextElement and hasNext accordingly. * * @since 2019-10-17 + * @since v0.3.0 */ private void getAndSetNextElement() { do { @@ -214,6 +220,7 @@ public final class ConditionalExistenceCollections { * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param key type * @param value type */ @@ -227,6 +234,7 @@ public final class ConditionalExistenceCollections { * @param map * @param entryExistenceCondition * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceMap(final Map map, final Predicate> entryExistenceCondition) { @@ -313,6 +321,7 @@ public final class ConditionalExistenceCollections { * * @author Adrien Hopkins * @since 2019-10-17 + * @since v0.3.0 * @param type of element in set */ static final class ConditionalExistenceSet extends AbstractSet { @@ -325,6 +334,7 @@ public final class ConditionalExistenceCollections { * @param set set to use * @param existenceCondition condition where element exists * @since 2019-10-17 + * @since v0.3.0 */ private ConditionalExistenceSet(final Set set, final Predicate existenceCondition) { @@ -410,6 +420,7 @@ public final class ConditionalExistenceCollections { * @param existenceCondition elements only exist if this returns true * @return wrapper collection * @since 2019-10-17 + * @since v0.3.0 */ public static final Collection conditionalExistenceCollection( final Collection collection, @@ -427,6 +438,7 @@ public final class ConditionalExistenceCollections { * @param existenceCondition elements only exist if this returns true * @return wrapper iterator * @since 2019-10-17 + * @since v0.3.0 */ public static final Iterator conditionalExistenceIterator( final Iterator iterator, final Predicate existenceCondition) { @@ -443,6 +455,7 @@ public final class ConditionalExistenceCollections { * @param entryExistenceCondition mappings only exist if this returns true * @return wrapper map * @since 2019-10-17 + * @since v0.3.0 */ public static final Map conditionalExistenceMap( final Map map, @@ -459,6 +472,7 @@ public final class ConditionalExistenceCollections { * @param existenceCondition elements only exist if this returns true * @return wrapper set * @since 2019-10-17 + * @since v0.3.0 */ public static final Set conditionalExistenceSet(final Set set, final Predicate existenceCondition) { diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index 4136818..c7564c4 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -193,6 +193,7 @@ public final class DecimalComparison { * @param b second value to test * @return whether they are equal * @since 2020-09-07 + * @since v0.3.0 */ public static final boolean equals(final UncertainDouble a, final UncertainDouble b) { diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 8ab6c95..1c8df9f 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -87,6 +87,7 @@ public final class ExpressionParser { * A map mapping operator strings to numeric functions. * * @since 2024-03-23 + * @since v0.5.0 */ private final Map> numericOperators; @@ -526,6 +527,7 @@ public final class ExpressionParser { * A map mapping operator strings to numeric functions. * * @since 2024-03-23 + * @since v0.5.0 */ private final Map> numericOperators; diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index c0c8f94..ebb1e8b 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -29,6 +29,7 @@ import java.util.Set; * * @author Adrien Hopkins * @since 2019-10-21 + * @since v0.3.0 */ public final class NameSymbol { /** The {@code NameSymbol} with all fields empty. */ @@ -67,6 +68,7 @@ public final class NameSymbol { * @param symbol symbol to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if name or symbol is null */ public static final NameSymbol of(final String name, final String symbol) { @@ -83,6 +85,7 @@ public final class NameSymbol { * @param otherNames other names to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if any argument is null */ public static final NameSymbol of(final String name, final String symbol, @@ -101,6 +104,7 @@ public final class NameSymbol { * @param otherNames other names to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if any argument is null */ public static final NameSymbol of(final String name, final String symbol, @@ -117,6 +121,7 @@ public final class NameSymbol { * @param name name to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if name is null */ public static final NameSymbol ofName(final String name) { @@ -140,6 +145,7 @@ public final class NameSymbol { * @param otherNames other names to use * @return NameSymbol instance * @since 2019-11-26 + * @since v0.3.0 */ public static final NameSymbol ofNullable(final String name, final String symbol, final Set otherNames) { @@ -163,6 +169,7 @@ public final class NameSymbol { * @param otherNames other names to use * @return NameSymbol instance * @since 2019-11-26 + * @since v0.3.0 */ public static final NameSymbol ofNullable(final String name, final String symbol, final String... otherNames) { @@ -176,6 +183,7 @@ public final class NameSymbol { * @param symbol symbol to use * @return NameSymbol instance * @since 2019-10-21 + * @since v0.3.0 * @throws NullPointerException if symbol is null */ public static final NameSymbol ofSymbol(final String symbol) { @@ -196,6 +204,7 @@ public final class NameSymbol { * @param otherNames other names and/or spellings, should be a mutable copy * of the argument * @since 2019-10-21 + * @since v0.3.0 */ NameSymbol(final Optional primaryName, final Optional symbol, final Set otherNames) { @@ -241,6 +250,7 @@ public final class NameSymbol { /** * @return otherNames * @since 2019-10-21 + * @since v0.3.0 */ public final Set getOtherNames() { return this.otherNames; @@ -249,6 +259,7 @@ public final class NameSymbol { /** * @return primaryName * @since 2019-10-21 + * @since v0.3.0 */ public final Optional getPrimaryName() { return this.primaryName; @@ -257,6 +268,7 @@ public final class NameSymbol { /** * @return symbol * @since 2019-10-21 + * @since v0.3.0 */ public final Optional getSymbol() { return this.symbol; @@ -302,8 +314,8 @@ public final class NameSymbol { * @param name additional name to add * @return copy of this NameSymbol with the additional name * - * @since v0.4.0 * @since 2022-04-19 + * @since v0.4.0 */ public final NameSymbol withExtraName(String name) { if (this.primaryName.isPresent()) { diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index 15026b7..efd1ab8 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -24,12 +24,14 @@ import java.util.Set; * and symbol data should be immutable. * * @since 2020-09-07 + * @since v0.3.0 */ public interface Nameable { /** * @return a name for the object - if there's a primary name, it's that, * otherwise the symbol, otherwise "Unnamed" * @since 2022-02-26 + * @since v0.4.0 */ default String getName() { final NameSymbol ns = this.getNameSymbol(); @@ -40,12 +42,14 @@ public interface Nameable { * @return a {@code NameSymbol} that contains this object's primary name, * symbol and other names * @since 2020-09-07 + * @since v0.3.0 */ NameSymbol getNameSymbol(); /** * @return set of alternate names * @since 2020-09-07 + * @since v0.3.0 */ default Set getOtherNames() { return this.getNameSymbol().getOtherNames(); @@ -54,6 +58,7 @@ public interface Nameable { /** * @return preferred name of object * @since 2020-09-07 + * @since v0.3.0 */ default Optional getPrimaryName() { return this.getNameSymbol().getPrimaryName(); @@ -63,6 +68,7 @@ public interface Nameable { * @return a short name for the object - if there's a symbol, it's that, * otherwise the symbol, otherwise "Unnamed" * @since 2022-02-26 + * @since v0.4.0 */ default String getShortName() { final NameSymbol ns = this.getNameSymbol(); @@ -72,6 +78,7 @@ public interface Nameable { /** * @return short symbol representing object * @since 2020-09-07 + * @since v0.3.0 */ default Optional getSymbol() { return this.getNameSymbol().getSymbol(); diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 772ff5e..1b8832e 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -33,6 +33,7 @@ import java.util.function.Function; * @author Adrien Hopkins * @param type of object that is being multiplied * @since 2019-10-16 + * @since v0.3.0 */ public class ObjectProduct implements Nameable { /** @@ -47,6 +48,7 @@ public class ObjectProduct implements Nameable { * @param type of objects that can be multiplied * @return empty product * @since 2019-10-16 + * @since v0.3.0 */ public static final ObjectProduct empty() { return new ObjectProduct<>(new HashMap<>()); @@ -59,6 +61,7 @@ public class ObjectProduct implements Nameable { * @param map map mapping objects to exponents * @return object product * @since 2019-10-16 + * @since v0.3.0 */ public static final ObjectProduct fromExponentMapping( final Map map) { @@ -73,6 +76,7 @@ public class ObjectProduct implements Nameable { * @param type of object contained in returned ObjectProduct * @return product * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if object is null */ public static final ObjectProduct oneOf(final T object) { @@ -87,6 +91,7 @@ public class ObjectProduct implements Nameable { * treats zero as null, and is immutable. * * @since 2019-10-16 + * @since v0.3.0 */ final Map exponents; @@ -100,6 +105,7 @@ public class ObjectProduct implements Nameable { * * @param exponents objects that make up this product * @since 2019-10-16 + * @since v0.3.0 */ ObjectProduct(final Map exponents) { this(exponents, NameSymbol.EMPTY); @@ -111,6 +117,7 @@ public class ObjectProduct implements Nameable { * @param exponents objects that make up this product * @param nameSymbol name and symbol of object product * @since 2019-10-16 + * @since v0.3.0 */ ObjectProduct(final Map exponents, NameSymbol nameSymbol) { this.exponents = Collections.unmodifiableMap( @@ -125,6 +132,7 @@ public class ObjectProduct implements Nameable { * @param other other product * @return quotient of two products * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if other is null */ public ObjectProduct dividedBy(final ObjectProduct other) { @@ -158,6 +166,7 @@ public class ObjectProduct implements Nameable { /** * @return immutable map mapping objects to exponents * @since 2019-10-16 + * @since v0.3.0 */ public Map exponentMap() { return this.exponents; @@ -209,6 +218,7 @@ public class ObjectProduct implements Nameable { * @return true if this product is a single object, i.e. it has one exponent * of one and no other nonzero exponents * @since 2019-10-16 + * @since v0.3.0 */ public boolean isSingleObject() { int oneCount = 0; @@ -229,6 +239,7 @@ public class ObjectProduct implements Nameable { * @param other other product * @return product of two products * @since 2019-10-16 + * @since v0.3.0 * @throws NullPointerException if other is null */ public ObjectProduct times(final ObjectProduct other) { @@ -254,6 +265,7 @@ public class ObjectProduct implements Nameable { * @param exponent exponent * @return result of exponentiation * @since 2019-10-16 + * @since v0.3.0 */ public ObjectProduct toExponent(final int exponent) { final Map map = new HashMap<>(this.exponents); @@ -274,6 +286,7 @@ public class ObjectProduct implements Nameable { * @return result of exponentiation * * @since 2024-08-22 + * @since v0.3.0 */ public ObjectProduct toExponentRounded(final double exponent) { final Map map = new HashMap<>(this.exponents); @@ -315,6 +328,7 @@ public class ObjectProduct implements Nameable { * @param objectToString function to convert objects to strings * @return string representation of product * @since 2019-10-16 + * @since v0.3.0 */ public String toString(final Function objectToString) { final List positiveStringComponents = new ArrayList<>(); @@ -347,6 +361,7 @@ public class ObjectProduct implements Nameable { * @return named version of this {@code ObjectProduct}, using data from * {@code nameSymbol} * @since 2021-12-15 + * @since v0.3.0 */ public ObjectProduct withName(NameSymbol nameSymbol) { return new ObjectProduct<>(this.exponents, nameSymbol); diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index bf198ae..cde3d37 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -39,8 +39,8 @@ import java.util.regex.Pattern; * are made * * - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public final class SemanticVersionNumber implements Comparable { @@ -52,8 +52,8 @@ public final class SemanticVersionNumber * throw NullPointerExceptions, everything else throws * IllegalArgumentException. * - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public static final class Builder { private final int major; @@ -69,8 +69,8 @@ public final class SemanticVersionNumber * @param major major version number of final version * @param minor minor version number of final version * @param patch patch version number of final version - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ private Builder(int major, int minor, int patch) { this.major = major; @@ -82,8 +82,8 @@ public final class SemanticVersionNumber /** * @return version number created by this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public SemanticVersionNumber build() { return new SemanticVersionNumber(this.major, this.minor, this.patch, @@ -95,8 +95,8 @@ public final class SemanticVersionNumber * * @param identifiers build metadata * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder buildMetadata(List identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -115,8 +115,8 @@ public final class SemanticVersionNumber * * @param identifiers build metadata * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder buildMetadata(String... identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -154,8 +154,8 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(int... identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -173,8 +173,8 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(List identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -193,8 +193,8 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(String... identifiers) { Objects.requireNonNull(identifiers, "identifiers may not be null"); @@ -214,8 +214,8 @@ public final class SemanticVersionNumber * @param identifier1 first identifier * @param identifier2 second identifier * @return this builder - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public Builder preRelease(String identifier1, int identifier2) { Objects.requireNonNull(identifier1, "identifier1 may not be null"); @@ -280,8 +280,8 @@ public final class SemanticVersionNumber * @param patch patch version number of final version * @return version number builder * @throws IllegalArgumentException if any argument is negative - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public static final SemanticVersionNumber.Builder builder(int major, int minor, int patch) { @@ -304,8 +304,8 @@ public final class SemanticVersionNumber * @param b second list * @return result of comparison as in a comparator * @see Comparator - * @since v0.4.0 * @since 2022-02-20 + * @since v0.4.0 */ private static final int compareIdentifiers(List a, List b) { // test pre-release size @@ -365,8 +365,8 @@ public final class SemanticVersionNumber * * @param versionString string to parse * @return {@code SemanticVersionNumber} instance - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 * @see #toString */ public static final SemanticVersionNumber fromString(String versionString) { @@ -409,8 +409,8 @@ public final class SemanticVersionNumber * * @param versionString string to test * @return true iff string is valid - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public static final boolean isValidVersionString(String versionString) { return VERSION_NUMBER.matcher(versionString).matches(); @@ -429,8 +429,8 @@ public final class SemanticVersionNumber * @throws IllegalArgumentException if any argument is negative or if the * preReleaseType is null, empty or not * alphanumeric (0-9, A-Z, a-z, - only) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public static final SemanticVersionNumber preRelease(int major, int minor, int patch, String preReleaseType, int preReleaseNumber) { @@ -467,8 +467,8 @@ public final class SemanticVersionNumber * @param patch patch version number * @return {@code SemanticVersionNumber} instance * @throws IllegalArgumentException if any argument is negative - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public static final SemanticVersionNumber stableVersion(int major, int minor, int patch) { @@ -500,8 +500,8 @@ public final class SemanticVersionNumber * @param patch patch version number * @param preReleaseIdentifiers pre-release version data * @param buildMetadata build metadata - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ private SemanticVersionNumber(int major, int minor, int patch, List preReleaseIdentifiers, List buildMetadata) { @@ -514,8 +514,8 @@ public final class SemanticVersionNumber /** * @return build metadata (empty if there is none) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public List buildMetadata() { return Collections.unmodifiableList(this.buildMetadata); @@ -585,8 +585,8 @@ public final class SemanticVersionNumber * @param other version to compare with * @return true if you can definitely upgrade to {@code other} without * changing code - * @since v0.4.0 * @since 2022-02-20 + * @since v0.4.0 */ public boolean compatibleWith(SemanticVersionNumber other) { Objects.requireNonNull(other, "other may not be null"); @@ -639,8 +639,8 @@ public final class SemanticVersionNumber /** * @return true iff this version is stable (major version > 0 and not a * pre-release) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public boolean isStable() { return this.major > 0 && this.preReleaseIdentifiers.isEmpty(); @@ -649,8 +649,8 @@ public final class SemanticVersionNumber /** * @return the MAJOR version number, incremented when you make backwards * incompatible API changes - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public int majorVersion() { return this.major; @@ -659,8 +659,8 @@ public final class SemanticVersionNumber /** * @return the MINOR version number, incremented when you add backwards * compatible functionality - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public int minorVersion() { return this.minor; @@ -669,8 +669,8 @@ public final class SemanticVersionNumber /** * @return the PATCH version number, incremented when you make backwards * compatible bug fixes - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public int patchVersion() { return this.patch; @@ -679,8 +679,8 @@ public final class SemanticVersionNumber /** * @return identifiers describing this pre-release (empty if not a * pre-release) - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public List preReleaseIdentifiers() { return Collections.unmodifiableList(this.preReleaseIdentifiers); diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index 46803d1..f700454 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -29,6 +29,7 @@ import java.util.regex.Pattern; * arguments is null. * * @since 2020-09-07 + * @since v0.3.0 */ public final class UncertainDouble implements Comparable { /** @@ -56,6 +57,7 @@ public final class UncertainDouble implements Comparable { * @throws NumberFormatException if the argument is not a number * * @since 2022-04-18 + * @since v0.4.0 */ public static final UncertainDouble fromRoundedString(String s) { final BigDecimal value = new BigDecimal(s); @@ -75,6 +77,7 @@ public final class UncertainDouble implements Comparable { * @return {@code UncertainDouble} instance * @throws IllegalArgumentException if the string is invalid * @since 2020-09-07 + * @since v0.3.0 */ public static final UncertainDouble fromString(String s) { Objects.requireNonNull(s, "s may not be null"); @@ -116,6 +119,7 @@ public final class UncertainDouble implements Comparable { * @return {@code UncertainDouble} instance with these parameters * * @since 2020-09-07 + * @since v0.3.0 */ public static final UncertainDouble of(double value, double uncertainty) { return new UncertainDouble(value, uncertainty); @@ -132,6 +136,7 @@ public final class UncertainDouble implements Comparable { * @return {@code UncertainDouble} instance with these parameters * * @since 2020-09-07 + * @since v0.3.0 */ public static final UncertainDouble ofRelative(double value, double relativeUncertainty) { @@ -146,6 +151,7 @@ public final class UncertainDouble implements Comparable { * @param value * @param uncertainty * @since 2020-09-07 + * @since v0.3.0 */ private UncertainDouble(double value, double uncertainty) { this.value = value; @@ -178,6 +184,7 @@ public final class UncertainDouble implements Comparable { * @return quotient * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble dividedBy(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); @@ -192,6 +199,7 @@ public final class UncertainDouble implements Comparable { * @return quotient * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble dividedByExact(double other) { return UncertainDouble.of(this.value / other, this.uncertainty / other); @@ -216,6 +224,7 @@ public final class UncertainDouble implements Comparable { * @return true iff this and {@code other} are within each other's * uncertainty range. * @since 2020-09-07 + * @since v0.3.0 */ public final boolean equivalent(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); @@ -227,6 +236,7 @@ public final class UncertainDouble implements Comparable { * Gets the preferred scale for rounding a value for toString. * * @since 2020-09-07 + * @since v0.3.0 */ private final int getDisplayScale() { // round based on uncertainty @@ -263,6 +273,7 @@ public final class UncertainDouble implements Comparable { * @return true iff the value has no uncertainty * * @since 2020-09-07 + * @since v0.3.0 */ public final boolean isExact() { return this.uncertainty == 0; @@ -275,6 +286,7 @@ public final class UncertainDouble implements Comparable { * @return result of subtraction * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble minus(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); @@ -289,6 +301,7 @@ public final class UncertainDouble implements Comparable { * @return result of subtraction * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble minusExact(double other) { return UncertainDouble.of(this.value - other, this.uncertainty); @@ -301,6 +314,7 @@ public final class UncertainDouble implements Comparable { * @return result of addition * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble plus(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); @@ -315,6 +329,7 @@ public final class UncertainDouble implements Comparable { * @return result of addition * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble plusExact(double other) { return UncertainDouble.of(this.value + other, this.uncertainty); @@ -323,6 +338,7 @@ public final class UncertainDouble implements Comparable { /** * @return relative uncertainty * @since 2020-09-07 + * @since v0.3.0 */ public final double relativeUncertainty() { return this.uncertainty / this.value; @@ -335,6 +351,7 @@ public final class UncertainDouble implements Comparable { * @return product * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble times(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); @@ -349,6 +366,7 @@ public final class UncertainDouble implements Comparable { * @return product * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble timesExact(double other) { return UncertainDouble.of(this.value * other, this.uncertainty * other); @@ -361,6 +379,7 @@ public final class UncertainDouble implements Comparable { * @return result of exponentation * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble toExponent(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); @@ -381,6 +400,7 @@ public final class UncertainDouble implements Comparable { * @return result of exponentation * * @since 2020-09-07 + * @since v0.3.0 */ public final UncertainDouble toExponentExact(double other) { return UncertainDouble.ofRelative(Math.pow(this.value, other), @@ -404,6 +424,7 @@ public final class UncertainDouble implements Comparable { * * * @since 2020-09-07 + * @since v0.3.0 */ @Override public final String toString() { @@ -441,6 +462,7 @@ public final class UncertainDouble implements Comparable { * @return string representation of this {@code UncertainDouble} * * @since 2020-09-07 + * @since v0.3.0 */ public final String toString(boolean showUncertainty, RoundingMode roundingMode) { @@ -473,6 +495,7 @@ public final class UncertainDouble implements Comparable { /** * @return absolute uncertainty * @since 2020-09-07 + * @since v0.3.0 */ public final double uncertainty() { return this.uncertainty; @@ -481,6 +504,7 @@ public final class UncertainDouble implements Comparable { /** * @return value without uncertainty * @since 2020-09-07 + * @since v0.3.0 */ public final double value() { return this.value; diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java index fbf78a3..97df107 100644 --- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java +++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java @@ -26,6 +26,7 @@ import sevenUnits.unit.UnitPrefix; * A rule that specifies whether prefix repetition is allowed * * @since 2020-08-26 + * @since v0.3.0 */ public enum DefaultPrefixRepetitionRule implements Predicate> { /** Prefix repetition is never allowed; only one prefix may be used. */ diff --git a/src/main/java/sevenUnitsGUI/DelegateListModel.java b/src/main/java/sevenUnitsGUI/DelegateListModel.java index 4925197..200eee2 100644 --- a/src/main/java/sevenUnitsGUI/DelegateListModel.java +++ b/src/main/java/sevenUnitsGUI/DelegateListModel.java @@ -56,6 +56,7 @@ final class DelegateListModel extends AbstractListModel * Creates an empty {@code DelegateListModel}. * * @since 2019-04-13 + * @since v0.2.0 */ public DelegateListModel() { this(new ArrayList<>()); diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java index ead07c5..20eb23c 100644 --- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java +++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java @@ -20,21 +20,21 @@ package sevenUnitsGUI; * A View that can convert unit expressions * * @author Adrien Hopkins - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ public interface ExpressionConversionView extends View { /** * @return unit expression to convert from - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ String getFromExpression(); /** * @return unit expression to convert to - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ String getToExpression(); @@ -42,8 +42,8 @@ public interface ExpressionConversionView extends View { * Shows the output of an expression conversion to the user. * * @param uc unit conversion to show - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ void showExpressionConversionOutput(UnitConversionRecord uc); } diff --git a/src/main/java/sevenUnitsGUI/Main.java b/src/main/java/sevenUnitsGUI/Main.java index b573a09..3ff2fd9 100644 --- a/src/main/java/sevenUnitsGUI/Main.java +++ b/src/main/java/sevenUnitsGUI/Main.java @@ -19,8 +19,8 @@ package sevenUnitsGUI; /** * The main code for the 7Units GUI * - * @since v0.4.0 * @since 2022-04-19 + * @since v0.4.0 */ public final class Main { @@ -28,8 +28,8 @@ public final class Main { * The main method that starts 7Units * * @param args commandline arguments - * @since v0.4.0 * @since 2022-04-19 + * @since v0.4.0 */ 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 1937fd2..2ea0923 100644 --- a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java +++ b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java @@ -34,8 +34,8 @@ import sevenUnits.unit.UnitPrefix; * A search rule that applies a certain set of prefixes to a unit. It always * includes the original unit in the output map. * - * @since v0.4.0 * @since 2022-07-06 + * @since v0.4.0 */ public final class PrefixSearchRule implements Function, Map> { @@ -70,8 +70,8 @@ public final class PrefixSearchRule implements * * @param prefixes prefixes to apply * @return prefix rule - * @since v0.4.0 * @since 2022-07-06 + * @since v0.4.0 */ public static final PrefixSearchRule getCoherentOnlyRule( Set prefixes) { @@ -84,8 +84,8 @@ public final class PrefixSearchRule implements * * @param prefixes prefixes to apply * @return prefix rule - * @since v0.4.0 * @since 2022-07-06 + * @since v0.4.0 */ public static final PrefixSearchRule getUniversalRule( Set prefixes) { @@ -106,8 +106,8 @@ public final class PrefixSearchRule implements * @param prefixes prefixes to add to units * @param prefixableUnitRule function that determines which units get * prefixes - * @since v0.4.0 * @since 2022-07-06 + * @since v0.4.0 */ public PrefixSearchRule(Set prefixes, Predicate prefixableUnitRule) { @@ -143,8 +143,8 @@ public final class PrefixSearchRule implements /** * @return rule that determines which units get prefixes - * @since v0.4.0 * @since 2022-07-09 + * @since v0.4.0 */ public Predicate getPrefixableUnitRule() { return this.prefixableUnitRule; @@ -152,8 +152,8 @@ public final class PrefixSearchRule implements /** * @return the prefixes that are applied by this rule - * @since v0.4.0 * @since 2022-07-06 + * @since v0.4.0 */ public Set getPrefixes() { return this.prefixes; diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 7c66d55..ff7e23c 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -60,6 +60,7 @@ import sevenUnitsGUI.StandardDisplayRules.UncertaintyBased; * * @author Adrien Hopkins * @since 2021-12-15 + * @since v0.4.0 */ public final class Presenter { /** @@ -139,6 +140,7 @@ public final class Presenter { * {@code maxLineLength}. If no good spot is found, returns -1. * * @since 2024-08-22 + * @since v1.0.0 */ private static int findLineSplit(String toWrap, int maxLineLength) { for (var i = maxLineLength - 1; i >= 0; i--) { @@ -155,6 +157,7 @@ public final class Presenter { * @param filename filename to get resource from * @return contents of file * @since 2021-03-27 + * @since v0.3.0 */ private static List getLinesFromResource(String filename) { final List lines = new ArrayList<>(); @@ -178,6 +181,7 @@ public final class Presenter { * @param filepath file to use as resource * @return obtained Path * @since 2021-03-27 + * @since v0.3.0 */ private static InputStream inputStream(String filepath) { return Presenter.class.getResourceAsStream(filepath); @@ -188,6 +192,7 @@ public final class Presenter { * the nearest integer. * * @since 2024-08-16 + * @since v1.0.0 */ private static String linearUnitValueIntToString(LinearUnitValue uv) { return Long.toString(Math.round(uv.getValueExact())) + " " + uv.getUnit(); @@ -227,6 +232,7 @@ public final class Presenter { /** * @return true iff a and b have any elements in common * @since 2022-04-19 + * @since v0.4.0 */ private static boolean sharesAnyElements(Set a, Set b) { for (final Object e : a) { @@ -254,6 +260,7 @@ public final class Presenter { /** * @return {@code line} with any comments removed. * @since 2021-03-13 + * @since v0.3.0 */ private static String withoutComments(String line) { final var index = line.indexOf('#'); @@ -264,6 +271,7 @@ public final class Presenter { * Wraps a string, ensuring no line is longer than {@code maxLineLength}. * * @since 2024-08-22 + * @since v1.0.0 */ private static String wrapString(String toWrap, int maxLineLength) { final var wrapped = new StringBuilder(toWrap.length()); @@ -366,6 +374,7 @@ public final class Presenter { * * @param view the view that this presenter communicates with * @since 2021-12-15 + * @since v0.4.0 */ public Presenter(View view) { this.view = view; @@ -449,6 +458,7 @@ public final class Presenter { * @param e entry * @return stream of entries, ready for flat-mapping * @since 2022-07-06 + * @since v0.4.0 */ private Stream> applySearchRule( Map.Entry e) { @@ -472,6 +482,7 @@ public final class Presenter { * not implement * {@link ExpressionConversionView}) * @since 2021-12-15 + * @since v0.4.0 */ public void convertExpressions() { if (!(this.view instanceof ExpressionConversionView)) @@ -515,6 +526,7 @@ public final class Presenter { * returned. * * @since 2024-08-15 + * @since v1.0.0 */ private Optional convertExpressionToExpression( String fromExpression, String toExpression) { @@ -567,6 +579,7 @@ public final class Presenter { * the view and Optional.empty() is returned. * * @since 2024-08-15 + * @since v1.0.0 */ private Optional convertExpressionToMultiUnit( String fromExpression, String[] toExpressions) { @@ -617,6 +630,7 @@ public final class Presenter { * error happened, it is shown to the view and Optional.empty() is returned. * * @since 2024-08-15 + * @since v1.0.0 */ private Optional convertExpressionToNamedMultiUnit( String fromExpression, String toName) { @@ -653,6 +667,7 @@ public final class Presenter { * implement * {@link UnitConversionView}) * @since 2021-12-15 + * @since v0.4.0 */ public void convertUnits() { if (!(this.view instanceof UnitConversionView)) @@ -770,6 +785,7 @@ public final class Presenter { /** * @return true iff duplicate units are shown in unit lists * @since 2022-03-30 + * @since v0.4.0 */ public boolean duplicatesShown() { return this.showDuplicates; @@ -778,6 +794,7 @@ public final class Presenter { /** * @return text in About file * @since 2022-02-19 + * @since v0.4.0 */ public String getAboutText() { final Path customFilepath = Presenter.pathFromConfig( @@ -809,6 +826,7 @@ public final class Presenter { /** * @return set of all locales available to select * @since 2025-02-21 + * @since v1.0.0 */ public Set getAvailableLocales() { return this.locales.keySet(); @@ -820,6 +838,7 @@ public final class Presenter { * @param dimension dimension to name * @return name of dimension * @since 2022-04-16 + * @since v0.4.0 */ String getDimensionName(ObjectProduct dimension) { // find this dimension in the database and get its name @@ -847,6 +866,7 @@ public final class Presenter { * @return the rule that is used by this presenter to convert numbers into * strings * @since 2022-04-10 + * @since v0.4.0 */ public Function getNumberDisplayRule() { return this.numberDisplayRule; @@ -856,6 +876,7 @@ public final class Presenter { * @return the rule that is used by this presenter to convert strings into * numbers * @since 2022-04-10 + * @since v0.4.0 */ @SuppressWarnings("unused") // not implemented yet private Function getNumberParsingRule() { @@ -865,6 +886,7 @@ public final class Presenter { /** * @return the rule that determines whether a set of prefixes is valid * @since 2022-04-19 + * @since v0.4.0 */ public Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; @@ -873,6 +895,7 @@ public final class Presenter { /** * @return the rule that determines which units are prefixed * @since 2022-07-08 + * @since v0.4.0 */ public Function, Map> getSearchRule() { return this.searchRule; @@ -881,6 +904,7 @@ public final class Presenter { /** * @return a search rule that shows all single prefixes * @since 2022-07-08 + * @since v0.4.0 */ public Function, Map> getUniversalSearchRule() { return PrefixSearchRule.getCoherentOnlyRule( @@ -890,6 +914,7 @@ public final class Presenter { /** * @return user's selected locale * @since 2025-02-21 + * @since v1.0.0 */ public String getUserLocale() { return userLocale; @@ -898,6 +923,7 @@ public final class Presenter { /** * @return the view associated with this presenter * @since 2022-04-19 + * @since v0.4.0 */ public View getView() { return this.view; @@ -908,6 +934,7 @@ public final class Presenter { * message and alerts the user. * * @since 2024-08-22 + * @since v1.0.0 */ private void handleLoadErrors(List errors) { if (!errors.isEmpty()) { @@ -925,6 +952,7 @@ public final class Presenter { * @return whether or not the provided unit is semi-metric (i.e. an * exception) * @since 2022-04-16 + * @since v0.4.0 */ boolean isSemiMetric(Unit u) { // determine if u is an exception @@ -944,6 +972,7 @@ public final class Presenter { * number display rule. * * @since 2024-08-16 + * @since v1.0.0 */ private String linearUnitValueSumToString(List values) { final var integerPart = values.subList(0, values.size() - 1).stream() @@ -1018,6 +1047,7 @@ public final class Presenter { * * @param settingsFile file settings should be loaded from * @since 2021-12-15 + * @since v0.4.0 */ void loadSettings(Path settingsFile) { this.customDimensionFiles.clear(); @@ -1086,6 +1116,7 @@ public final class Presenter { /** * @return a message showing how much stuff has been loaded * @since 2024-08-22 + * @since v1.0.0 */ private String loadStatMsg() { return this.getLocalizedText("load_stat_msg") @@ -1107,6 +1138,7 @@ public final class Presenter { * unit list and imperial/USC units removed from the To unit list) * * @since 2022-03-30 + * @since v0.4.0 */ public boolean oneWayConversionEnabled() { return this.oneWayConversionEnabled; @@ -1118,6 +1150,7 @@ public final class Presenter { * they depend on are not created yet. * * @since 2022-02-26 + * @since v0.4.0 */ public void postViewInitialize() { // unit conversion specific stuff @@ -1148,6 +1181,7 @@ public final class Presenter { * * @return false iff the presenter could not write to the file * @since 2022-04-19 + * @since v0.4.0 */ public boolean saveSettings() { final var configDir = CONFIG_FILE.getParent(); @@ -1187,6 +1221,7 @@ public final class Presenter { * @param numberDisplayRule the new rule that will be used by this presenter * to convert numbers into strings * @since 2022-04-10 + * @since v0.4.0 */ public void setNumberDisplayRule( Function numberDisplayRule) { @@ -1197,6 +1232,7 @@ public final class Presenter { * @param numberParsingRule the new rule that will be used by this presenter * to convert strings into numbers * @since 2022-04-10 + * @since v0.4.0 */ @SuppressWarnings("unused") // not implemented yet private void setNumberParsingRule( @@ -1208,6 +1244,7 @@ public final class Presenter { * @param oneWayConversionEnabled whether not one-way conversion should be * enabled * @since 2022-03-30 + * @since v0.4.0 * @see #oneWayConversionEnabled */ public void setOneWayConversionEnabled(boolean oneWayConversionEnabled) { @@ -1219,6 +1256,7 @@ public final class Presenter { * @param prefixRepetitionRule the rule that determines whether a set of * prefixes is valid * @since 2022-04-19 + * @since v0.4.0 */ public void setPrefixRepetitionRule( Predicate> prefixRepetitionRule) { @@ -1232,6 +1270,7 @@ public final class Presenter { * unit (including the unit itself) that should be * searchable. * @since 2022-07-08 + * @since v0.4.0 */ public void setSearchRule( Function, Map> searchRule) { @@ -1259,6 +1298,7 @@ public final class Presenter { /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 + * @since v0.4.0 */ public void setShowDuplicates(boolean showDuplicateUnits) { this.showDuplicates = showDuplicateUnits; @@ -1304,6 +1344,7 @@ public final class Presenter { * * @param u unit to show * @since 2022-04-16 + * @since v0.4.0 */ private void showUnit(Unit u) { final var nameSymbol = u.getNameSymbol(); @@ -1320,6 +1361,7 @@ public final class Presenter { * description of a unit and displays it. * * @since 2022-04-10 + * @since v0.4.0 */ void unitNameSelected() { // get selected unit, if it's there and valid @@ -1335,6 +1377,7 @@ public final class Presenter { * Updates the view's From and To units, if it has some * * @since 2021-12-15 + * @since v0.4.0 */ public void updateView() { if (this.view instanceof UnitConversionView) { @@ -1402,6 +1445,7 @@ public final class Presenter { * @return AssertionError stating that an error has happened in the view's * code * @since 2022-04-09 + * @since v0.4.0 */ private AssertionError viewError(String message, Object... args) { return new AssertionError("View Programming Error (from " + this.view @@ -1413,6 +1457,7 @@ public final class Presenter { * * @param settingsFile file settings should be saved to * @since 2021-12-15 + * @since v0.4.0 */ boolean writeSettings(Path settingsFile) { try (var writer = Files.newBufferedWriter(settingsFile)) { diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java index 1748083..43a57ce 100644 --- a/src/main/java/sevenUnitsGUI/SearchBoxList.java +++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java @@ -84,6 +84,7 @@ final class SearchBoxList extends JPanel { * Creates an empty SearchBoxList * * @since 2022-02-19 + * @since v0.4.0 */ public SearchBoxList() { this(List.of(), null, false); @@ -94,6 +95,7 @@ final class SearchBoxList extends JPanel { * * @param itemsToFilter items to put in the list * @since 2019-04-14 + * @since v0.2.0 */ public SearchBoxList(final Collection itemsToFilter) { this(itemsToFilter, null, false); @@ -170,6 +172,7 @@ final class SearchBoxList extends JPanel { * @return items available in search list, including items that are hidden by * the search filter * @since 2022-03-30 + * @since v0.4.0 */ public Collection getItems() { return Collections.unmodifiableCollection(this.itemsToFilter); @@ -323,6 +326,7 @@ final class SearchBoxList extends JPanel { * * @param newItems new items to put in list * @since 2021-05-22 + * @since v0.3.0 */ public void setItems(Collection newItems) { this.itemsToFilter.clear(); @@ -334,6 +338,7 @@ final class SearchBoxList extends JPanel { * Manually updates the search box's item list. * * @since 2020-08-27 + * @since v0.3.0 */ public void updateList() { this.searchBoxTextChanged(); diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java index a19b680..d710117 100644 --- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java +++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java @@ -28,15 +28,15 @@ import sevenUnits.utils.UncertainDouble; * A static utility class that can be used to make display rules for the * presenter. * - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ public final class StandardDisplayRules { /** * A rule that rounds to a fixed number of decimal places. * - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ public static final class FixedDecimals implements Function { @@ -51,6 +51,7 @@ public final class StandardDisplayRules { /** * @param decimalPlaces * @since 2022-04-18 + * @since v0.4.0 */ private FixedDecimals(int decimalPlaces) { this.decimalPlaces = decimalPlaces; @@ -66,6 +67,7 @@ public final class StandardDisplayRules { /** * @return the number of decimal places this rule rounds to * @since 2022-04-18 + * @since v0.4.0 */ public int decimalPlaces() { return this.decimalPlaces; @@ -97,8 +99,8 @@ public final class StandardDisplayRules { /** * A rule that rounds to a fixed number of significant digits. * - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ public static final class FixedPrecision implements Function { @@ -114,6 +116,7 @@ public final class StandardDisplayRules { /** * @param significantFigures * @since 2022-04-18 + * @since v0.4.0 */ private FixedPrecision(int significantFigures) { this.mathContext = new MathContext(significantFigures, @@ -150,6 +153,7 @@ public final class StandardDisplayRules { /** * @return the number of significant figures this rule rounds to * @since 2022-04-18 + * @since v0.4.0 */ public int significantFigures() { return this.mathContext.getPrecision(); @@ -167,8 +171,8 @@ public final class StandardDisplayRules { * This means the output will have around as many significant figures as the * input. * - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ public static final class UncertaintyBased implements Function { @@ -195,8 +199,8 @@ public final class StandardDisplayRules { /** * @param decimalPlaces decimal places to round to * @return a rounding rule that rounds to fixed number of decimal places - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ public static final FixedDecimals fixedDecimals(int decimalPlaces) { return new FixedDecimals(decimalPlaces); @@ -206,8 +210,8 @@ public final class StandardDisplayRules { * @param significantFigures significant figures to round to * @return a rounding rule that rounds to a fixed number of significant * figures - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ public static final FixedPrecision fixedPrecision(int significantFigures) { return new FixedPrecision(significantFigures); @@ -220,8 +224,8 @@ public final class StandardDisplayRules { * @return display rule * @throws IllegalArgumentException if the provided string is not that of a * standard rule. - * @since v0.4.0 * @since 2021-12-24 + * @since v0.4.0 */ public static final Function getStandardRule( String ruleToString) { @@ -246,8 +250,8 @@ public final class StandardDisplayRules { /** * @return an UncertainDouble-based rounding rule - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ public static final UncertaintyBased uncertaintyBased() { return UNCERTAINTY_BASED_ROUNDING_RULE; diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 97b93dc..1afaf33 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -67,8 +67,8 @@ import sevenUnits.utils.UncertainDouble; /** * A View that separates its functions into multiple tabs * - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** @@ -76,8 +76,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { * * @param type of item in list * - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ private static final class JComboBoxItemSet extends AbstractSet { private final JComboBox comboBox; @@ -85,6 +85,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** * @param comboBox combo box to get items from * @since 2022-02-19 + * @since v0.4.0 */ public JComboBoxItemSet(JComboBox comboBox) { this.comboBox = comboBox; @@ -122,8 +123,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { * The standard types of rounding, corresponding to the options on the * TabbedView's settings panel. * - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ private static enum StandardRoundingType { /** @@ -147,8 +148,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { * Creates a TabbedView. * * @param args command line arguments - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public static void main(String[] args) { // This view doesn't need to do anything, the side effects of creating it @@ -209,8 +210,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** * Creates the view and makes it visible to the user * - * @since v0.4.0 * @since 2022-02-19 + * @since v0.4.0 */ public TabbedView() { // enable system look and feel @@ -401,6 +402,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { * code more organized, as this function is massive!) * * @since 2022-02-19 + * @since v0.4.0 */ private JPanel createSettingsPanel() { final JPanel settingsPanel = new JPanel(); @@ -737,8 +739,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** * @return the precision of the presenter's rounding rule, if that is * meaningful - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ private OptionalInt getPresenterPrecision() { final var presenterRule = this.presenter.getNumberDisplayRule(); @@ -756,8 +758,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** * @return presenter's prefix repetition rule - * @since v0.4.0 * @since 2022-04-19 + * @since v0.4.0 */ private Optional getPresenterPrefixRule() { final var prefixRule = this.presenter.getPrefixRepetitionRule(); @@ -769,8 +771,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** * Determines which rounding type the presenter is currently using, if any. * - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ private Optional getPresenterRoundingType() { final var presenterRule = this.presenter.getNumberDisplayRule(); @@ -881,8 +883,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { * Sets the presenter's rounding rule to the one specified by the current * settings * - * @since v0.4.0 * @since 2022-04-18 + * @since v0.4.0 */ private void updatePresenterRoundingRule() { final Function roundingRule; diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java index 6dde230..958deae 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java @@ -24,8 +24,8 @@ import sevenUnits.unit.UnitValue; /** * A record of a conversion between units or expressions * - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public final class UnitConversionRecord { /** @@ -34,8 +34,8 @@ public final class UnitConversionRecord { * @param input input unit & value * @param output output unit & value * @return unit conversion record - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public static UnitConversionRecord fromLinearValues(LinearUnitValue input, LinearUnitValue output) { @@ -51,8 +51,8 @@ public final class UnitConversionRecord { * @param input input unit & value * @param output output unit & value * @return unit conversion record - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public static UnitConversionRecord fromValues(UnitValue input, UnitValue output) { @@ -70,8 +70,8 @@ public final class UnitConversionRecord { * @param inputValueString string representing input value * @param outputValueString string representing output value * @return unit conversion record - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public static UnitConversionRecord valueOf(String fromName, String toName, String inputValueString, String outputValueString) { @@ -106,6 +106,7 @@ public final class UnitConversionRecord { * @param inputValueString string representing input value * @param outputValueString string representing output value * @since 2022-04-09 + * @since v0.4.0 */ private UnitConversionRecord(String fromName, String toName, String inputValueString, String outputValueString) { @@ -147,8 +148,8 @@ public final class UnitConversionRecord { /** * @return name of unit or expression that was converted from - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public String fromName() { return this.fromName; @@ -171,8 +172,8 @@ public final class UnitConversionRecord { /** * @return string representing input value - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public String inputValueString() { return this.inputValueString; @@ -180,8 +181,8 @@ public final class UnitConversionRecord { /** * @return string representing output value - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public String outputValueString() { return this.outputValueString; @@ -189,8 +190,8 @@ public final class UnitConversionRecord { /** * @return name of unit or expression that was converted to - * @since v0.4.0 * @since 2022-04-09 + * @since v0.4.0 */ public String toName() { return this.toName; diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index a6cc399..c7ffda4 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -23,57 +23,57 @@ import java.util.Set; * A View that supports single unit-based conversion * * @author Adrien Hopkins - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ public interface UnitConversionView extends View { /** * @return dimensions available for filtering - * @since v0.4.0 * @since 2022-01-29 + * @since v0.4.0 */ Set getDimensionNames(); /** * @return name of unit to convert from - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ Optional getFromSelection(); /** * @return list of names of units available to convert from - * @since v0.4.0 * @since 2022-03-30 + * @since v0.4.0 */ Set getFromUnitNames(); /** * @return value to convert between the units (specifically, the numeric * string provided by the user) - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ String getInputValue(); /** * @return selected dimension - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ Optional getSelectedDimensionName(); /** * @return name of unit to convert to - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ Optional getToSelection(); /** * @return list of names of units available to convert to - * @since v0.4.0 * @since 2022-03-30 + * @since v0.4.0 */ Set getToUnitNames(); @@ -81,8 +81,8 @@ public interface UnitConversionView extends View { * Sets the available dimensions for filtering. * * @param dimensionNames names of dimensions to use - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ void setDimensionNames(Set dimensionNames); @@ -92,8 +92,8 @@ public interface UnitConversionView extends View { * that allow the user to select units from a list. * * @param unitNames names of units to convert from - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ void setFromUnitNames(Set unitNames); @@ -103,8 +103,8 @@ public interface UnitConversionView extends View { * that allow the user to select units from a list. * * @param unitNames names of units to convert to - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ void setToUnitNames(Set unitNames); @@ -112,8 +112,8 @@ public interface UnitConversionView extends View { * Shows the output of a unit conversion. * * @param uc record of unit conversion - * @since v0.4.0 * @since 2021-12-24 + * @since v0.4.0 */ void showUnitConversionOutput(UnitConversionRecord uc); } diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index f934bb5..fc04593 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -26,14 +26,14 @@ import sevenUnits.utils.NameSymbol; * An object that controls user interaction with 7Units * * @author Adrien Hopkins - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ public interface View { /** * @return a new tabbed view - * @since v0.4.0 * @since 2022-04-19 + * @since v0.4.0 */ static View createTabbedView() { return new TabbedView(); @@ -41,22 +41,22 @@ public interface View { /** * @return the presenter associated with this view - * @since v0.4.0 * @since 2022-04-19 + * @since v0.4.0 */ Presenter getPresenter(); /** * @return name of prefix currently being viewed - * @since v0.4.0 * @since 2022-04-10 + * @since v0.4.0 */ Optional getViewedPrefixName(); /** * @return name of unit currently being viewed - * @since v0.4.0 * @since 2022-04-10 + * @since v0.4.0 */ Optional getViewedUnitName(); @@ -65,8 +65,8 @@ public interface View { * viewer * * @param prefixNames prefix names to view - * @since v0.4.0 * @since 2022-04-10 + * @since v0.4.0 */ void setViewablePrefixNames(Set prefixNames); @@ -74,8 +74,8 @@ public interface View { * Sets the list of units that are available to be viewed in a unit viewer * * @param unitNames unit names to view - * @since v0.4.0 * @since 2022-04-10 + * @since v0.4.0 */ void setViewableUnitNames(Set unitNames); @@ -85,8 +85,8 @@ public interface View { * @param title title of error message; on any view that uses an error * dialog, this should be the title of the error dialog. * @param message error message - * @since v0.4.0 * @since 2021-12-15 + * @since v0.4.0 */ void showErrorMessage(String title, String message); @@ -95,8 +95,8 @@ public interface View { * * @param name name(s) and symbol of prefix * @param multiplierString string representation of prefix multiplier - * @since v0.4.0 * @since 2022-04-10 + * @since v0.4.0 */ void showPrefix(NameSymbol name, String multiplierString); @@ -107,8 +107,8 @@ public interface View { * @param definition unit's definition string * @param dimensionName name of unit's dimension * @param type type of unit (metric/semi-metric/non-metric) - * @since v0.4.0 * @since 2022-04-10 + * @since v0.4.0 */ void showUnit(NameSymbol name, String definition, String dimensionName, UnitType type); diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index 8e24e64..689b460 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -32,8 +32,8 @@ import sevenUnits.utils.Nameable; * for testing. Getters and setters work as expected. * * @author Adrien Hopkins - * @since v0.4.0 * @since 2022-01-29 + * @since v0.4.0 */ public final class ViewBot implements UnitConversionView, ExpressionConversionView { @@ -42,6 +42,7 @@ public final class ViewBot * {@link View#showPrefix(NameSymbol, String)}, for testing. * * @since 2022-04-16 + * @since v0.4.0 */ public static final class PrefixViewingRecord implements Nameable { private final NameSymbol nameSymbol; @@ -51,6 +52,7 @@ public final class ViewBot * @param nameSymbol * @param multiplierString * @since 2022-04-16 + * @since v0.4.0 */ public PrefixViewingRecord(NameSymbol nameSymbol, String multiplierString) { @@ -106,6 +108,7 @@ public final class ViewBot * {@link View#showUnit(NameSymbol, String, String, UnitType)}, for testing. * * @since 2022-04-16 + * @since v0.4.0 */ public static final class UnitViewingRecord implements Nameable { private final NameSymbol nameSymbol; @@ -119,6 +122,7 @@ public final class ViewBot * @param dimensionName name of unit's dimension * @param unitType type of unit (metric/semi-metric/non-metric) * @since 2022-04-16 + * @since v0.4.0 */ public UnitViewingRecord(NameSymbol nameSymbol, String definition, String dimensionName, UnitType unitType) { @@ -131,6 +135,7 @@ public final class ViewBot /** * @return the definition * @since 2022-04-16 + * @since v0.4.0 */ public String definition() { return this.definition; @@ -139,6 +144,7 @@ public final class ViewBot /** * @return the dimensionName * @since 2022-04-16 + * @since v0.4.0 */ public String dimensionName() { return this.dimensionName; @@ -160,6 +166,7 @@ public final class ViewBot /** * @return the nameSymbol * @since 2022-04-16 + * @since v0.4.0 */ @Override public NameSymbol getNameSymbol() { @@ -195,6 +202,7 @@ public final class ViewBot /** * @return the unitType * @since 2022-04-16 + * @since v0.4.0 */ public UnitType unitType() { return this.unitType; @@ -243,6 +251,7 @@ public final class ViewBot * Creates a new {@code ViewBot} with a new presenter. * * @since 2022-01-29 + * @since v0.4.0 */ public ViewBot() { this.presenter = new Presenter(this); @@ -256,6 +265,7 @@ public final class ViewBot /** * @return list of records of expression conversions done by this bot * @since 2022-04-09 + * @since v0.4.0 */ public List expressionConversionList() { return Collections.unmodifiableList(this.expressionConversions); @@ -264,6 +274,7 @@ public final class ViewBot /** * @return the available dimensions * @since 2022-01-29 + * @since v0.4.0 */ @Override public Set getDimensionNames() { @@ -283,6 +294,7 @@ public final class ViewBot /** * @return the units available for selection in From * @since 2022-01-29 + * @since v0.4.0 */ @Override public Set getFromUnitNames() { @@ -297,6 +309,7 @@ public final class ViewBot /** * @return the presenter associated with tihs view * @since 2022-01-29 + * @since v0.4.0 */ @Override public Presenter getPresenter() { @@ -321,6 +334,7 @@ public final class ViewBot /** * @return the units available for selection in To * @since 2022-01-29 + * @since v0.4.0 */ @Override public Set getToUnitNames() { @@ -340,6 +354,7 @@ public final class ViewBot /** * @return list of records of this viewBot's prefix views * @since 2022-04-16 + * @since v0.4.0 */ public List prefixViewList() { return Collections.unmodifiableList(this.prefixViewingRecords); @@ -357,6 +372,7 @@ public final class ViewBot * @param fromExpression the expression to convert from * @throws NullPointerException if {@code fromExpression} is null * @since 2022-01-29 + * @since v0.4.0 */ public void setFromExpression(String fromExpression) { this.fromExpression = Objects.requireNonNull(fromExpression, @@ -366,6 +382,7 @@ public final class ViewBot /** * @param fromSelection the fromSelection to set * @since 2022-01-29 + * @since v0.4.0 */ public void setFromSelection(Optional fromSelection) { this.fromSelection = Objects.requireNonNull(fromSelection, @@ -375,6 +392,7 @@ public final class ViewBot /** * @param fromSelection the fromSelection to set * @since 2022-02-10 + * @since v0.4.0 */ public void setFromSelection(String fromSelection) { this.setFromSelection(Optional.of(fromSelection)); @@ -388,6 +406,7 @@ public final class ViewBot /** * @param inputValue the inputValue to set * @since 2022-01-29 + * @since v0.4.0 */ public void setInputValue(String inputValue) { this.inputValue = inputValue; @@ -396,6 +415,7 @@ public final class ViewBot /** * @param selectedDimensionName the selectedDimensionName to set * @since 2022-01-29 + * @since v0.4.0 */ public void setSelectedDimensionName( Optional selectedDimensionName) { @@ -416,6 +436,7 @@ public final class ViewBot * @param toExpression the expression to convert to * @throws NullPointerException if {@code toExpression} is null * @since 2022-01-29 + * @since v0.4.0 */ public void setToExpression(String toExpression) { this.toExpression = Objects.requireNonNull(toExpression, @@ -425,6 +446,7 @@ public final class ViewBot /** * @param toSelection unit set in the 'To' selection * @since 2022-01-29 + * @since v0.4.0 */ public void setToSelection(Optional toSelection) { this.toSelection = Objects.requireNonNull(toSelection, @@ -521,6 +543,7 @@ public final class ViewBot /** * @return list of records of every unit conversion made by this bot * @since 2022-04-09 + * @since v0.4.0 */ public List unitConversionList() { return Collections.unmodifiableList(this.unitConversions); @@ -529,6 +552,7 @@ public final class ViewBot /** * @return list of records of unit viewings made by this bot * @since 2022-04-16 + * @since v0.4.0 */ public List unitViewList() { return Collections.unmodifiableList(this.unitViewingRecords); diff --git a/src/main/java/sevenUnitsGUI/package-info.java b/src/main/java/sevenUnitsGUI/package-info.java index c0ccc1b..74ec18c 100644 --- a/src/main/java/sevenUnitsGUI/package-info.java +++ b/src/main/java/sevenUnitsGUI/package-info.java @@ -19,5 +19,6 @@ * * @author Adrien Hopkins * @since 2021-12-15 + * @since v0.4.0 */ package sevenUnitsGUI; \ No newline at end of file diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 56296b4..c78837f 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -60,6 +60,7 @@ class UnitDatabaseTest { /** * * @since 2021-10-07 + * @since v0.3.2 */ public SimpleEntry(K key, V value) { this.key = key; @@ -136,6 +137,7 @@ class UnitDatabaseTest { * @param value value in entry * @return entry * @since 2021-10-07 + * @since v0.3.2 */ private static Map.Entry entry(K key, V value) { return new SimpleEntry<>(key, value); @@ -149,6 +151,7 @@ class UnitDatabaseTest { * @param path path of file to load * @return exceptions returned by file loading * @since 2021-10-04 + * @since v0.3.2 */ private static List loadDimensionFile(UnitDatabase loadTo, String path) { @@ -169,6 +172,7 @@ class UnitDatabaseTest { * @param path path of file to load * @return exceptions returned by file loading * @since 2021-09-22 + * @since v0.3.2 */ private static List loadUnitsFile(UnitDatabase loadTo, String path) { @@ -226,6 +230,7 @@ class UnitDatabaseTest { * function. Simple because the expression parser has its own test. * * @since 2021-09-27 + * @since v0.3.2 */ @ParameterizedTest @MethodSource @@ -254,6 +259,7 @@ class UnitDatabaseTest { * and {@link UnitDatabase#getLinearUnitValue}. * * @since 2021-10-07 + * @since v0.3.2 */ @Test public void testGetUnit() { @@ -287,6 +293,7 @@ class UnitDatabaseTest { * throw an {@code IllegalStateException}. * * @since 2019-05-03 + * @since v0.3.0 */ // @Test // @Timeout(value = 1, unit = TimeUnit.SECONDS) @@ -313,6 +320,7 @@ class UnitDatabaseTest { * * @param num which file to test * @since 2021-10-04 + * @since v0.3.2 */ @ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) @@ -335,6 +343,7 @@ class UnitDatabaseTest { * * @param num which file to test * @since 2021-09-27 + * @since v0.3.2 */ @ParameterizedTest @ValueSource(ints = { 1, 2, 3, 4, 5 }) @@ -353,6 +362,7 @@ class UnitDatabaseTest { * Tests loading a valid dimension-file with some derived dimensions. * * @since 2021-10-04 + * @since v0.3.2 */ @Test public void testLoadingValidDimensions() { @@ -371,6 +381,7 @@ class UnitDatabaseTest { * Tests loading a valid unitfile with some prefixes and no units. * * @since 2021-09-22 + * @since v0.3.2 */ @Test public void testLoadingValidPrefixes() { @@ -390,6 +401,7 @@ class UnitDatabaseTest { * Tests loading a valid unitfile with some units and preloaded prefixes * * @since 2021-09-22 + * @since v0.3.2 */ @Test public void testLoadingValidUnits() { @@ -436,6 +448,7 @@ class UnitDatabaseTest { * the unit map iterator is simple. * * @since 2021-10-07 + * @since v0.3.2 */ @Test public void testPrefixedUnitMapIterator() { @@ -735,6 +748,7 @@ class UnitDatabaseTest { * Tests the ability to create, read, and delete unit sets. * * @since 2025-04-30 + * @since v1.0.0 */ @Test void testUnitSetsInvalid() { @@ -765,6 +779,7 @@ class UnitDatabaseTest { * Tests the ability to create, read, and delete unit sets. * * @since 2025-04-30 + * @since v1.0.0 */ @Test void testUnitSetsValid() { diff --git a/src/test/java/sevenUnits/unit/UnitValueTest.java b/src/test/java/sevenUnits/unit/UnitValueTest.java index 6b80986..6182b20 100644 --- a/src/test/java/sevenUnits/unit/UnitValueTest.java +++ b/src/test/java/sevenUnits/unit/UnitValueTest.java @@ -34,6 +34,11 @@ import org.junit.jupiter.params.provider.MethodSource; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.UncertainDouble; +/** + * Tests for the UnitValue and LinearUnitValue classes + * + * @since v1.0.0 + */ public final class UnitValueTest { private static Stream testConvertToMultiple() { return Stream.of( @@ -160,6 +165,7 @@ public final class UnitValueTest { * Tests converting an uncertain LinearUnitValue to a string. * * @since 2021-11-04 + * @since v0.3.2 */ @Test public void testValueToString1() { @@ -176,6 +182,7 @@ public final class UnitValueTest { * Tests converting a certain LinearUnitValue to a string. * * @since 2021-11-04 + * @since v0.3.2 */ @Test public void testValueToString2() { @@ -192,6 +199,7 @@ public final class UnitValueTest { * Tests converting an unnamed LinearUnitValue to a string. * * @since 2021-11-04 + * @since v0.3.2 */ @Test public void testValueToString3() { @@ -207,6 +215,7 @@ public final class UnitValueTest { * Tests converting a named UnitValue to a string. * * @since 2021-11-04 + * @since v0.3.2 */ @Test public void testValueToString4() { @@ -219,6 +228,7 @@ public final class UnitValueTest { * Tests converting an unnamed UnitValue to a string. * * @since 2021-11-04 + * @since v0.3.2 */ @Test public void testValueToString5() { diff --git a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java index 29675de..ea96574 100644 --- a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java +++ b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java @@ -41,6 +41,7 @@ import sevenUnits.utils.ConditionalExistenceCollections.ConditionalExistenceIter * * @author Adrien Hopkins * @since 2019-10-16 + * @since v0.3.0 */ class ConditionalExistenceCollectionsTest { @@ -49,6 +50,7 @@ class ConditionalExistenceCollectionsTest { * * @return test iterator * @since 2019-10-17 + * @since v0.3.0 */ ConditionalExistenceIterator getTestIterator() { final List items = Arrays.asList("aa", "ab", "ba"); @@ -63,6 +65,7 @@ class ConditionalExistenceCollectionsTest { * * @return map to be used for test data * @since 2019-10-16 + * @since v0.3.0 */ Map getTestMap() { final Map map = new HashMap<>(); diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java index 463880b..72d3b19 100644 --- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java +++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java @@ -65,6 +65,7 @@ class ExpressionParserTest { * @return A stream of objects, where each one is an expression and the * expected result * @since 2021-09-27 + * @since v0.3.2 */ private static final Stream testParseExpressionData() { return IntStream.range(0, TEST_EXPRESSIONS.size()) diff --git a/src/test/java/sevenUnits/utils/NameSymbolTest.java b/src/test/java/sevenUnits/utils/NameSymbolTest.java index 55e09c4..3ae2448 100644 --- a/src/test/java/sevenUnits/utils/NameSymbolTest.java +++ b/src/test/java/sevenUnits/utils/NameSymbolTest.java @@ -31,6 +31,11 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +/** + * Tests for the {@link NameSymbol} class. + * + * @since v1.0.0 + */ class NameSymbolTest { private static Stream testEqualsHashCode() { return Stream.of( diff --git a/src/test/java/sevenUnits/utils/SemanticVersionTest.java b/src/test/java/sevenUnits/utils/SemanticVersionTest.java index 295ad39..3bef773 100644 --- a/src/test/java/sevenUnits/utils/SemanticVersionTest.java +++ b/src/test/java/sevenUnits/utils/SemanticVersionTest.java @@ -35,12 +35,14 @@ import org.junit.jupiter.api.Test; * Tests for {@link SemanticVersionNumber} * * @since 2022-02-19 + * @since v0.4.0 */ public final class SemanticVersionTest { /** * Test for {@link SemanticVersionNumber#compatible} * * @since 2022-02-20 + * @since v0.4.0 */ @Test public void testCompatibility() { @@ -66,6 +68,7 @@ public final class SemanticVersionTest { * Tests {@link SemanticVersionNumber#toString} for complex version numbers * * @since 2022-02-19 + * @since v0.4.0 */ @Test public void testComplexToString() { @@ -84,6 +87,7 @@ public final class SemanticVersionTest { * Tests that complex version can be created and their parts read * * @since 2022-02-19 + * @since v0.4.0 */ @Test public void testComplexVersions() { @@ -116,6 +120,7 @@ public final class SemanticVersionTest { * Test that semantic version strings can be parsed correctly * * @since 2022-02-19 + * @since v0.4.0 * @see SemanticVersionNumber#fromString * @see SemanticVersionNumber#isValidVersionString */ @@ -267,6 +272,7 @@ public final class SemanticVersionTest { * Test for {@link SemanticVersionNumber#isStable} * * @since 2022-02-19 + * @since v0.4.0 */ @Test public void testIsStable() { @@ -290,6 +296,7 @@ public final class SemanticVersionTest { * and some more. * * @since 2022-02-19 + * @since v0.4.0 */ @Test public void testOrder() { @@ -350,6 +357,7 @@ public final class SemanticVersionTest { * Tests that simple stable versions can be created and their parts read * * @since 2022-02-19 + * @since v0.4.0 */ @Test public void testSimpleStableVersions() { @@ -369,6 +377,7 @@ public final class SemanticVersionTest { * numbers * * @since 2022-02-19 + * @since v0.4.0 */ @Test public void testSimpleToString() { @@ -383,6 +392,7 @@ public final class SemanticVersionTest { * Tests that simple unstable versions can be created and their parts read * * @since 2022-02-19 + * @since v0.4.0 */ @Test public void testSimpleUnstableVersions() { diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java index fe66e2d..518c818 100644 --- a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java +++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; * * @author Adrien Hopkins * @since 2021-11-29 + * @since v0.3.2 */ class UncertainDoubleTest { /** @@ -77,6 +78,7 @@ class UncertainDoubleTest { * Test for {@link UncertainDouble#fromRoundedString} * * @since 2022-04-18 + * @since v0.4.0 */ @Test final void testFromRoundedString() { diff --git a/src/test/java/sevenUnitsGUI/I18nTest.java b/src/test/java/sevenUnitsGUI/I18nTest.java index 2875db6..1513373 100644 --- a/src/test/java/sevenUnitsGUI/I18nTest.java +++ b/src/test/java/sevenUnitsGUI/I18nTest.java @@ -20,12 +20,17 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; +/** + * Tests for the internationalization system. + * + * @since v1.0.0 + */ class I18nTest { /** * Tests that the default locale exists. * - * Currently this test fails. + * @since v1.0.0 */ @Test void testDefaultLocale() { diff --git a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java index ce75cca..50b390b 100644 --- a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java +++ b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java @@ -31,15 +31,15 @@ import sevenUnits.unit.Metric; /** * Tests for the default prefix repetition rules. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ class PrefixRepetitionTest { /** * Ensures that the complex repetition rule disallows invalid prefix lists. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testInvalidComplexRepetition() { @@ -58,8 +58,8 @@ class PrefixRepetitionTest { /** * Tests the {@code NO_REPETITION} rule. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testNoRepetition() { @@ -72,8 +72,8 @@ class PrefixRepetitionTest { /** * Tests the {@code NO_RESTRICTION} rule. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testNoRestriction() { @@ -86,8 +86,8 @@ class PrefixRepetitionTest { /** * Ensures that the complex repetition rule allows valid prefix lists. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testValidComplexRepetition() { diff --git a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java index c7c652b..b605d05 100644 --- a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java +++ b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java @@ -36,8 +36,8 @@ import sevenUnits.unit.Metric; /** * Tests for {@link PrefixSearchRule} * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ class PrefixSearchTest { /** @@ -52,8 +52,8 @@ class PrefixSearchTest { * {@link sevenUnitsGUI.PrefixSearchRule#apply(java.util.Map.Entry)}, for a * coherent unit and {@link PrefixSearchRule#COMMON_PREFIXES}. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test final void testCoherentPrefixSearch() { @@ -70,8 +70,8 @@ class PrefixSearchTest { * Test method for * {@link sevenUnitsGUI.PrefixSearchRule#equals(java.lang.Object)}. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test final void testEquals() { @@ -85,8 +85,8 @@ class PrefixSearchTest { /** * Test method for {@link sevenUnitsGUI.PrefixSearchRule#getPrefixes()}. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test final void testGetPrefixes() { @@ -98,8 +98,8 @@ class PrefixSearchTest { /** * Test method for {@link sevenUnitsGUI.PrefixSearchRule#hashCode()}. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test final void testHashCode() { @@ -111,8 +111,8 @@ class PrefixSearchTest { * Tests prefix searching for a non-coherent unit and * {@link PrefixSearchRule#COMMON_PREFIXES}. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test final void testNonCoherentPrefixSearch() { @@ -126,8 +126,8 @@ class PrefixSearchTest { /** * Tests that {@link PrefixSearchRule#NO_PREFIXES} returns the original unit. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testNoPrefixes() { @@ -146,8 +146,8 @@ class PrefixSearchTest { /** * Test method for {@link sevenUnitsGUI.PrefixSearchRule#toString()}. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test final void testToString() { diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 20d0c8a..9ac5b84 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -55,8 +55,8 @@ import sevenUnits.utils.UncertainDouble; * * @author Adrien Hopkins * - * @since v0.4.0 * @since 2022-02-10 + * @since v0.4.0 */ public final class PresenterTest { private static final Path TEST_SETTINGS = Path.of("src", "test", "resources", @@ -73,8 +73,8 @@ public final class PresenterTest { /** * @return rounding rules used by {@link #testRoundingRules} - * @since v0.4.0 * @since 2022-04-16 + * @since v0.4.0 */ private static Stream> getRoundingRules() { final var SCIENTIFIC_ROUNDING = StandardDisplayRules.uncertaintyBased(); @@ -108,8 +108,8 @@ public final class PresenterTest { /** * Test method for {@link Presenter#convertExpressions} * - * @since v0.4.0 * @since 2022-02-12 + * @since v0.4.0 */ @ParameterizedTest @MethodSource @@ -132,8 +132,8 @@ public final class PresenterTest { /** * Test method for {@link Presenter#convertUnits} * - * @since v0.4.0 * @since 2022-02-12 + * @since v0.4.0 */ @ParameterizedTest @MethodSource @@ -158,8 +158,8 @@ public final class PresenterTest { /** * Ensures that the default unitfile can be disabled. * - * @since v1.0.0 * @since 2025-02-23 + * @since v1.0.0 */ @Test void testDisableDefault() { @@ -175,8 +175,8 @@ public final class PresenterTest { /** * Tests that duplicate units are successfully removed, if that is asked for * - * @since v0.4.0 * @since 2022-04-16 + * @since v0.4.0 */ @Test void testDuplicateUnits() { @@ -208,8 +208,8 @@ public final class PresenterTest { /** * Tests that one-way conversion correctly filters From and To units * - * @since v0.4.0 * @since 2022-04-16 + * @since v0.4.0 */ @Test void testOneWayConversion() { @@ -242,8 +242,8 @@ public final class PresenterTest { /** * Tests the prefix-viewing functionality. * - * @since v0.4.0 * @since 2022-04-16 + * @since v0.4.0 */ @Test void testPrefixViewing() { @@ -272,8 +272,8 @@ public final class PresenterTest { /** * Tests that rounding rules are used correctly. * - * @since v0.4.0 * @since 2022-04-16 + * @since v0.4.0 */ @ParameterizedTest @MethodSource("getRoundingRules") @@ -301,8 +301,8 @@ public final class PresenterTest { * Tests that the Presenter correctly applies search rules. * * @param searchRule search rule to test - * @since v0.4.0 * @since 2022-07-08 + * @since v0.4.0 */ @ParameterizedTest @MethodSource("getSearchRules") @@ -335,8 +335,8 @@ public final class PresenterTest { /** * Tests that settings can be saved to and loaded from a file. * - * @since v0.4.0 * @since 2022-04-16 + * @since v0.4.0 */ @Test void testSettingsSaving() { @@ -369,8 +369,8 @@ public final class PresenterTest { /** * Ensures the Presenter generates the correct data upon a unit-viewing. * - * @since v0.4.0 * @since 2022-04-16 + * @since v0.4.0 */ @Test void testUnitViewing() { @@ -403,8 +403,8 @@ public final class PresenterTest { /** * Test for {@link Presenter#updateView()} * - * @since v0.4.0 * @since 2022-02-12 + * @since v0.4.0 */ @Test void testUpdateView() { diff --git a/src/test/java/sevenUnitsGUI/RoundingTest.java b/src/test/java/sevenUnitsGUI/RoundingTest.java index 535167c..e6453f2 100644 --- a/src/test/java/sevenUnitsGUI/RoundingTest.java +++ b/src/test/java/sevenUnitsGUI/RoundingTest.java @@ -41,8 +41,8 @@ import sevenUnitsGUI.StandardDisplayRules.UncertaintyBased; /** * Tests that ensure the rounding rules work as intended. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ class RoundingTest { // rounding rules to test @@ -69,8 +69,8 @@ class RoundingTest { /** * @return arguments for * {@link #testFixedDecimalRounding(UncertainDouble, String, String, String)} - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ private static final Stream fixedDecimalRoundingExamples() { // input, zero decimal string, two decimal string, six decimal string @@ -83,8 +83,8 @@ class RoundingTest { /** * @return arguments for * {@link #testFixedPrecisionRounding(UncertainDouble, String, String, String)} - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ private static final Stream fixedPrecisionRoundingExamples() { // input, one sig fig string, three s.f. string, six s.f. string @@ -97,8 +97,8 @@ class RoundingTest { /** * @return arguments for * {@link #testUncertaintyRounding(UncertainDouble, String)} - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ private static final Stream uncertaintyRoundingExamples() { // input, uncertainty rounding string @@ -112,8 +112,8 @@ class RoundingTest { * Test for {@link FixedDecimals#decimalPlaces()} and * {@link FixedPrecision#significantFigures()}. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testDataMethods() { @@ -138,8 +138,8 @@ class RoundingTest { /** * Tests that the rounding methods' equals() methods work. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testEquals() { @@ -174,6 +174,7 @@ class RoundingTest { * @param twoDecimalString expected string for two decimal places * @param sixDecimalString expected string for six decimal places * @since 2022-07-17 + * @since v0.4.0 */ @ParameterizedTest @MethodSource("fixedDecimalRoundingExamples") @@ -200,8 +201,8 @@ class RoundingTest { * @param oneSigFigString expected string for one significant figure * @param threeSigFigString expected string for three significant figures * @param twelveSigFigString expected string for twelve significant figures - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @ParameterizedTest @MethodSource("fixedPrecisionRoundingExamples") @@ -226,8 +227,8 @@ class RoundingTest { * Tests that {@link StandardDisplayRules#getStandardRule} gets rounding * rules as intended. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testGetStandardRule() { @@ -244,8 +245,8 @@ class RoundingTest { /** * Tests that the rounding methods' equals() methods work. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testHashCode() { @@ -258,8 +259,8 @@ class RoundingTest { * Tests that the {@code toString()} methods of the three rounding rule * classes work correctly. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testToString() { @@ -273,8 +274,8 @@ class RoundingTest { * * @param input input number * @param output expected output string - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @ParameterizedTest @MethodSource("uncertaintyRoundingExamples") diff --git a/src/test/java/sevenUnitsGUI/TabbedViewTest.java b/src/test/java/sevenUnitsGUI/TabbedViewTest.java index 7819452..3716673 100644 --- a/src/test/java/sevenUnitsGUI/TabbedViewTest.java +++ b/src/test/java/sevenUnitsGUI/TabbedViewTest.java @@ -26,16 +26,16 @@ import org.junit.jupiter.api.Timeout; /** * Test for the TabbedView. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Timeout(value = 10, unit = TimeUnit.SECONDS) class TabbedViewTest { /** * @return a view with all settings set to standard values * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ private static final TabbedView setupView() { final var view = new TabbedView(); @@ -55,8 +55,8 @@ class TabbedViewTest { * Simulates an expression conversion operation, and ensures it works * properly. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testExpressionConversion() { @@ -76,8 +76,8 @@ class TabbedViewTest { /** * Simulates a unit conversion operation, and ensures it works properly. * - * @since v0.4.0 * @since 2022-07-17 + * @since v0.4.0 */ @Test void testUnitConversion() { -- cgit v1.2.3 From 06192835ea97a657da1e5bd160686d21c097cbf5 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 4 Jun 2025 19:59:10 -0500 Subject: Add more tests for internationalization It seems the I18nTest class was incomplete - I just had one test, checking that English exists. I guess my coverage-based approach didn't catch it. --- src/test/java/sevenUnitsGUI/I18nTest.java | 60 +++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/test/java/sevenUnitsGUI/I18nTest.java b/src/test/java/sevenUnitsGUI/I18nTest.java index 1513373..78f5da4 100644 --- a/src/test/java/sevenUnitsGUI/I18nTest.java +++ b/src/test/java/sevenUnitsGUI/I18nTest.java @@ -16,9 +16,15 @@ */ package sevenUnitsGUI; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.util.stream.Stream; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests for the internationalization system. @@ -26,17 +32,65 @@ import org.junit.jupiter.api.Test; * @since v1.0.0 */ class I18nTest { + private static final Stream testLocalization() { + return Stream.of( + Arguments.of("tv.title", "en", "7Units [v]"), + Arguments.of("tv.title", "fr", "7Unités [v]"), + Arguments.of("tv.convert_units.title", "en", "Convert Units"), + Arguments.of("tv.convert_units.title", "fr", "Convertir Unités")); + } + + private static final Stream testLocaleSupported() { + return Stream.of("en", "fr"); + } /** - * Tests that the default locale exists. + * Tests that the default locale is supported. * * @since v1.0.0 + * @see Presenter#DEFAULT_LOCALE */ @Test - void testDefaultLocale() { + void testDefaultLocaleSupported() { Presenter p = new Presenter(new ViewBot()); assertNotNull(p.locales.get(Presenter.DEFAULT_LOCALE), - "Default locale does not exist."); + "Default locale is not supported."); + } + + /** + * Ensures that the system supports the provided locale. + * + * @param localeName locale to test for support + * + * @since 2025-06-04 + * @since v1.0.0 + */ + @ParameterizedTest + @MethodSource + void testLocaleSupported(String localeName) { + Presenter p = new Presenter(new ViewBot()); + assertNotNull(p.locales.get(localeName), + "Locale \"" + localeName + "\" is not supported."); + } + + /** + * Tests that the system can correctly localize text, + * using the default locales. + * + * @param key key of text to localize + * @param locale locale to use + * @param expected expected value of output text + * + * @since 2025-06-04 + * @since v1.0.0 + */ + @ParameterizedTest + @MethodSource + void testLocalization(String key, String locale, String expected) { + Presenter p = new Presenter(new ViewBot()); + p.setUserLocale(locale); + String actual = p.getLocalizedText(key); + assertEquals(expected, actual); } } -- cgit v1.2.3 From a00ad7ca48928a30ae577aeaed0345680df0a3fe Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 7 Jun 2025 22:11:45 -0500 Subject: Fix e-notation & consolidate expression parsing This commit moves all of the expression formatting code to one method, and changes it so that it works with things like '1e+2'. This does mean that I had to require spaces for addition and subtraction, but without that, the rules would be complicated. --- docs/manual.org | 8 +- docs/manual.pdf | Bin 186518 -> 188582 bytes docs/manual.tex | 40 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 574 +++++++++------------ .../java/sevenUnits/utils/ExpressionParser.java | 3 +- .../java/sevenUnits/unit/UnitDatabaseTest.java | 29 +- 6 files changed, 305 insertions(+), 349 deletions(-) diff --git a/docs/manual.org b/docs/manual.org index 92160c3..bc58ceb 100644 --- a/docs/manual.org +++ b/docs/manual.org @@ -98,12 +98,12 @@ You can also use the special setting names ~custom_unit_file~, ~custom_dimension * Appendices ** Unit Expressions A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence): -- Exponentiation (^); the exponent must be an integer. Both units and numbers can be raised to an exponent -- Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter"). +- Exponentiation (^); the exponent must be an integer. Both units and numbers can be raised to an exponent. +- Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter"), and multiplication using spaces has higher precedence than division. Therefore, "2 m / 1 m" is the number 2, while "2 * m / 1 * m" is equal to 2 m^2. You can also divide with ~|~ to create fractions. Using ~|~ instead of ~/~ gives the division a higher precedence than any other operator. For example, "2|5^2" evaluates to 4/25, not 2/25. -- Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds. +- Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds. *You must use spaces between terms when adding or subtracting - "1 + 2" is valid, "1+2" is not. This only applies to addition and subtraction - other operators do not need spaces between them.* This is done to avoid complex rules when working with negative numbers and e-notation. - Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \deg C + Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \deg{}C ** Other Expressions There are also a simplified version of expressions for prefixes and dimensions. Only multiplication, division and exponentation are supported. Currently, exponentation is not supported for dimensions, but that may be fixed in the future. * Footnotes diff --git a/docs/manual.pdf b/docs/manual.pdf index 38d5c66..ca31104 100644 Binary files a/docs/manual.pdf and b/docs/manual.pdf differ diff --git a/docs/manual.tex b/docs/manual.tex index a1f7c63..8ab09d5 100644 --- a/docs/manual.tex +++ b/docs/manual.tex @@ -1,4 +1,4 @@ -% Created 2025-06-01 Sun 20:01 +% Created 2025-06-07 Sat 18:16 % Intended LaTeX compiler: pdflatex \documentclass[11pt]{article} \usepackage[utf8]{inputenc} @@ -30,10 +30,10 @@ \newpage \section{Introduction and Purpose} -\label{sec:org6fb5a20} +\label{sec:orgf5013f4} 7Units is a program that can be used to convert units. This document outlines how to use the program. \section{System Requirements} -\label{sec:org9d01e55} +\label{sec:org9c3bf6a} \begin{itemize} \item Works on all major operating systems \\[0pt] \textbf{NOTE:} All screenshots in this document were taken on Windows 10. If you use a different operating system, the program will probably look different than what is shown. @@ -43,9 +43,9 @@ \end{itemize} \newpage \section{How to Use 7Units} -\label{sec:org1fd2398} +\label{sec:org6a030cf} \subsection{Simple Unit Conversion} -\label{sec:orga7e83a8} +\label{sec:org4406ca2} \begin{enumerate} \item Select the "Convert Units" tab if it is not already selected. You should see a screen like in figure \ref{main-interface-dimension}: \begin{figure}[htbp] @@ -70,7 +70,7 @@ \end{figure} \end{enumerate} \subsection{Complex Unit Conversion} -\label{sec:orgf923c07} +\label{sec:org721af5f} \begin{enumerate} \item Select the "Convert Unit Expressions" if it is not already selected. You should see a screen like in figure \ref{main-interface-expression}: \begin{figure}[htbp] @@ -78,7 +78,7 @@ \includegraphics[height=250px]{../screenshots/main-interface-expression-converter.png} \caption{\label{main-interface-expression}Taken in version 0.3.0} \end{figure} -\item Enter a \hyperref[sec:orga2dae79]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}". +\item Enter a \hyperref[sec:orgfffe912]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}". \item Enter a unit name (or another unit expression) in the To box. \item Press the Convert button. This will calculate the value of the first expression, and convert it to a multiple of the second unit (or expression). \begin{figure}[htbp] @@ -88,7 +88,7 @@ \end{figure} \end{enumerate} \section{7Units Settings} -\label{sec:org69dc7d4} +\label{sec:org437288e} All settings can be accessed in the tab with the gear icon. \begin{figure}[htbp] \centering @@ -96,7 +96,7 @@ All settings can be accessed in the tab with the gear icon. \caption{The settings menu, as of version 0.4.0} \end{figure} \subsection{Rounding Settings} -\label{sec:org8d63000} +\label{sec:orgc275b0f} These settings control how the output of a unit conversion is rounded. \begin{description} \item[{Fixed Precision}] Round to a fixed number of \href{https://en.wikipedia.org/wiki/Significant\_figures}{significant digits}. The number of significant digits is controlled by the precision slider below. @@ -104,7 +104,7 @@ These settings control how the output of a unit conversion is rounded. \item[{Scientific Precision}] Intelligent rounding which uses the precision of the input value(s) to determine the output precision. Not affected by the precision slider. \end{description} \subsection{Prefix Repetition Settings} -\label{sec:org1f1263f} +\label{sec:org1789e5a} These settings control when you are allowed to repeat unit prefixes (e.g. kilokilometre) \begin{description} \item[{No Repetition}] Units may only have one prefix. @@ -119,7 +119,7 @@ These settings control when you are allowed to repeat unit prefixes (e.g. kiloki \end{itemize} \end{description} \subsection{Search Settings} -\label{sec:org03df615} +\label{sec:orgfcf1ac1} These settings control which prefixes are shown in the "Convert Units" tab. Only coherent SI units (e.g. metre, second, newton, joule) will get prefixes. Some prefixed units are created in the unitfile, and will stay regardless of this setting (though they can be removed from the unitfile). \begin{description} \item[{Never Include Prefixed Units}] Prefixed units will only be shown if they are explicitly added to the unitfile. @@ -127,7 +127,7 @@ These settings control which prefixes are shown in the "Convert Units" tab. Onl \item[{Include All Single Prefixes}] Every coherent unit will have every prefixed version of it included in the list. \end{description} \subsection{Miscellaneous Settings} -\label{sec:org159a151} +\label{sec:org2a48ec0} \begin{description} \item[{Convert One Way Only}] In the simple conversion tab, only imperial/customary units will be shown on the left, and only metric units\footnote{7Units's definition of "metric" is stricter than the SI, but all of the common units that are commonly considered metric but not included in 7Units's definition are included in the exceptions file.} will be shown on the right. Units listed in the exceptions file (\texttt{src/main/resources/metric\_exceptions.txt}) will be shown on both sides. This is a way to reduce the number of options you must search through if you only convert one way. The expressions tab is unaffected. \item[{Show Duplicates in "Convert Units"}] If unchecked, any unit that has multiple names will only have one included in the Convert Units lists. The selected name will be the longest; if there are multiple longest names one is selected arbitrarily. You will still be able to use these alternate names in the expressions tab. @@ -135,7 +135,7 @@ These settings control which prefixes are shown in the "Convert Units" tab. Onl \item[{Locale}] Which language is used for the interface of 7Units. Custom locales can be added. \end{description} \subsection{Configuration File} -\label{sec:org398eb26} +\label{sec:orgd976cbf} The settings are saved in a configuration file. On Windows, this is located at \\[0pt] \texttt{\%USERPROFILE\%/AppData/Local/SevenUnits/config.txt}. On other operating systems, this is located at \texttt{\$HOME/.config/SevenUnits/config.txt}. The directory containing the \texttt{SevenUnits} directory can be overridden with the environment variables \texttt{\$LOCALAPPDATA} on Windows or \texttt{\$XDG\_CONFIG\_HOME} elsewhere. @@ -157,19 +157,19 @@ This is the name of a locale file, excluding the extension, in one of the direct You can also use the special setting names \texttt{custom\_unit\_file}, \texttt{custom\_dimension\_file} and \texttt{custom\_exception\_file} to add custom units, dimensions and metric exceptions to the system. These files use the same format as the standard files. These setting names can be used more than once to include multiple unit, dimension or exception files. \section{Appendices} -\label{sec:org89d72bb} +\label{sec:orgec4e8a9} \subsection{Unit Expressions} -\label{sec:orga2dae79} +\label{sec:orgfffe912} A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence): \begin{itemize} -\item Exponentiation (\^{}); the exponent must be an integer. Both units and numbers can be raised to an exponent -\item Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter"). +\item Exponentiation (\^{}); the exponent must be an integer. Both units and numbers can be raised to an exponent. +\item Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter"), and multiplication using spaces has higher precedence than division. Therefore, "2 m / 1 m" is the number 2, while "2 * m / 1 * m" is equal to 2 m\textsuperscript{2}. You can also divide with \texttt{|} to create fractions. Using \texttt{|} instead of \texttt{/} gives the division a higher precedence than any other operator. For example, "2|5\textsuperscript{2}" evaluates to 4/25, not 2/25. -\item Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds. +\item Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds. \textbf{You must use spaces between terms when adding or subtracting - "1 + 2" is valid, "1+2" is not. This only applies to addition and subtraction - other operators do not need spaces between them.} This is done to avoid complex rules when working with negative numbers and e-notation. -Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \textdegree{} C +Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \textdegree{}C \end{itemize} \subsection{Other Expressions} -\label{sec:orgf67cbc9} +\label{sec:org5d2d129} There are also a simplified version of expressions for prefixes and dimensions. Only multiplication, division and exponentation are supported. Currently, exponentation is not supported for dimensions, but that may be fixed in the future. \end{document} diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index b0d026f..a85ec5f 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -122,7 +122,7 @@ public final class UnitDatabase { implements Entry { private final String key; private final Unit value; - + /** * Creates the {@code PrefixedUnitEntry}. * @@ -135,7 +135,7 @@ public final class UnitDatabase { this.key = key; this.value = value; } - + /** * @since 2019-05-03 * @since v0.3.0 @@ -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 * @since v0.3.0 @@ -169,13 +169,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 @@ -190,7 +190,7 @@ public final class UnitDatabase { return this.getKey() + "=" + this.getValue(); } } - + /** * An iterator that iterates over the units of a * {@code PrefixedUnitNameSet}. @@ -205,12 +205,12 @@ public final class UnitDatabase { private int unitNamePosition = 0; // the indices of the prefixes attached to the current unit private final List prefixCoordinates = new ArrayList<>(); - + // values from the unit entry set private final Map map; private transient final List unitNames; private transient final List prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -225,7 +225,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 @@ -237,10 +237,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()) @@ -253,7 +253,7 @@ public final class UnitDatabase { return true; } } - + /** * Changes this iterator's position to the next available one. * @@ -262,11 +262,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); @@ -275,7 +275,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()) { @@ -283,7 +283,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 @@ -294,18 +294,18 @@ public final class UnitDatabase { } } } - + @Override public Entry next() { // get next element final Entry nextEntry = this.peek(); - + // iterate to next position this.incrementPosition(); - + return nextEntry; } - + /** * @return the next element in the iterator, without iterating over * it @@ -315,7 +315,7 @@ public final class UnitDatabase { private Entry peek() { if (!this.hasNext()) throw new NoSuchElementException("No units left!"); - + // if I have prefixes, ensure I'm not using a nonlinear unit // since all of the unprefixed stuff is done, just remove // nonlinear units @@ -326,12 +326,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. @@ -346,10 +346,10 @@ public final class UnitDatabase { this.peek()); } } - + // the map that created this set private final PrefixedUnitMap map; - + /** * Creates the {@code PrefixedUnitNameSet}. * @@ -360,31 +360,31 @@ public final class UnitDatabase { public PrefixedUnitEntrySet(final PrefixedUnitMap map) { this.map = map; } - + @Override public boolean add(final Map.Entry e) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public boolean addAll( final Collection> c) { throw new UnsupportedOperationException( "Cannot add to an immutable set"); } - + @Override public void clear() { throw new UnsupportedOperationException( "Cannot clear an immutable set"); } - + @Override public boolean contains(final Object o) { // get the entry final Entry entry; - + try { // This is OK because I'm in a try-catch block, catching the // exact exception that would be thrown. @@ -395,11 +395,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) @@ -407,42 +407,42 @@ public final class UnitDatabase { return false; return true; } - + @Override public boolean isEmpty() { return this.map.isEmpty(); } - + @Override public Iterator> iterator() { return new PrefixedUnitEntryIterator(this.map); } - + @Override public boolean remove(final Object o) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeAll(final Collection c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeIf( final Predicate> 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()) @@ -455,7 +455,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -468,7 +468,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 */ @@ -481,7 +481,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()) @@ -492,7 +492,7 @@ public final class UnitDatabase { this.map.units, this.map.prefixes); } } - + /** * The class used for unit name sets. * @@ -524,12 +524,12 @@ public final class UnitDatabase { private int unitNamePosition = 0; // the indices of the prefixes attached to the current unit private final List prefixCoordinates = new ArrayList<>(); - + // values from the unit name set private final Map map; private transient final List unitNames; private transient final List prefixNames; - + /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. @@ -544,7 +544,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 @@ -556,10 +556,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()) @@ -572,7 +572,7 @@ public final class UnitDatabase { return true; } } - + /** * Changes this iterator's position to the next available one. * @@ -581,11 +581,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); @@ -594,7 +594,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()) { @@ -602,7 +602,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 @@ -613,16 +613,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 @@ -642,10 +642,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. @@ -660,10 +660,10 @@ public final class UnitDatabase { this.peek()); } } - + // the map that created this set private final PrefixedUnitMap map; - + /** * Creates the {@code PrefixedUnitNameSet}. * @@ -674,30 +674,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 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) @@ -705,41 +705,41 @@ public final class UnitDatabase { return false; return true; } - + @Override public boolean isEmpty() { return this.map.isEmpty(); } - + @Override public Iterator iterator() { return new PrefixedUnitNameIterator(this.map); } - + @Override public boolean remove(final Object o) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeAll(final Collection c) { throw new UnsupportedOperationException( "Cannot remove from an immutable set"); } - + @Override public boolean removeIf(final Predicate 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()) @@ -752,7 +752,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + /** * @throws IllegalStateException if the set is infinite in size */ @@ -764,9 +764,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 */ @@ -779,7 +779,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()) @@ -790,7 +790,7 @@ public final class UnitDatabase { this.map.units, this.map.prefixes); } } - + /** * The units stored in this collection, without prefixes. * @@ -798,7 +798,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map units; - + /** * The available prefixes for use. * @@ -806,12 +806,12 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map prefixes; - + // caches private transient Collection values = null; private transient Set keySet = null; private transient Set> entrySet = null; - + /** * Creates the {@code PrefixedUnitMap}. * @@ -827,50 +827,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 remappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfAbsent(final String key, final Function mappingFunction) { throw new UnsupportedOperationException( "Cannot edit an immutable map"); } - + @Override public Unit computeIfPresent(final String key, final BiFunction 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) @@ -889,10 +889,10 @@ public final class UnitDatabase { } } } - + return longestPrefix != null; } - + /** * {@inheritDoc} * @@ -905,7 +905,7 @@ public final class UnitDatabase { public boolean containsValue(final Object value) { return this.units.containsValue(value); } - + @Override public Set> entrySet() { if (this.entrySet == null) { @@ -913,23 +913,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) @@ -948,7 +948,7 @@ public final class UnitDatabase { } } } - + // if none found, returns null if (longestPrefix == null) return null; @@ -959,16 +959,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 keySet() { if (this.keySet == null) { @@ -976,64 +976,64 @@ public final class UnitDatabase { } return this.keySet; } - + @Override public Unit merge(final String key, final Unit value, final BiFunction 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 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 function) { throw new UnsupportedOperationException( "Cannot replace entries in an immutable map"); } - + @Override public int size() { if (this.units.isEmpty()) @@ -1046,7 +1046,7 @@ public final class UnitDatabase { return Integer.MAX_VALUE; } } - + @Override public String toString() { if (this.units.isEmpty() || this.prefixes.isEmpty()) @@ -1056,7 +1056,7 @@ public final class UnitDatabase { "Infinite map of name-unit entries created from units %s and prefixes %s", this.units, this.prefixes); } - + /** * {@inheritDoc} * @@ -1074,48 +1074,20 @@ public final class UnitDatabase { return this.values; } } - - /** - * Replacements done to *all* expression types - */ - private static final Map EXPRESSION_REPLACEMENTS = new HashMap<>(); - - // add data to expression replacements - static { - // add spaces around operators - for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) { - EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator), - " " + operator + " "); - } - - // replace multiple spaces with a single space - EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " "); - // place brackets around any expression of the form "number unit", with or - // without the space - EXPRESSION_REPLACEMENTS.put(Pattern.compile("((?:-?[1-9]\\d*|0)" // integer - + "(?:\\.\\d+(?:[eE]\\d+))?)" // optional decimal point with numbers - // after it - + "\\s*" // optional space(s) - + "([a-zA-Z]+(?:\\^\\d+)?" // any string of letters - + "(?:\\s+[a-zA-Z]+(?:\\^\\d+)?))" // optional other letters - + "(?!-?\\d)" // no number directly afterwards (avoids matching - // "1e3") - ), "\\($1 $2\\)"); - } - + /** * A regular expression that separates names and expressions in unit files. */ private static final Pattern NAME_EXPRESSION = Pattern .compile("(\\S+)\\s+(\\S.*)"); - + /** * Like normal string comparisons, but shorter strings are always less than * longer strings. */ private static final Comparator lengthFirstComparator = Comparator .comparingInt(String::length).thenComparing(Comparator.naturalOrder()); - + /** * The exponent operator * @@ -1131,11 +1103,11 @@ public final class UnitDatabase { throw new IllegalArgumentException(String.format( "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", base, exponentUnit)); - + final double exponent = exponentUnit.getConversionFactor(); return base.toExponentRounded(exponent); } - + /** * The exponent operator * @@ -1151,11 +1123,33 @@ public final class UnitDatabase { throw new IllegalArgumentException(String.format( "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", base, exponentValue)); - + final double exponent = exponentValue.getValueExact(); return base.toExponentRounded(exponent); } - + + /** + * Formats an expression so it can be parsed by the expression parser. + * + * Specifically, puts spaces around all operators so they can be parsed as + * words. + * + * @param expression expression to format + * @return formatted expression + * @since 2025-06-07 + * @since v1.0.0 + */ + static final String formatExpression(String expression) { + String modifiedExpression = expression; + for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) { + modifiedExpression = modifiedExpression.replaceAll( + operator, " " + operator + " "); + } + + modifiedExpression = modifiedExpression.replaceAll("\\s+", " "); + return modifiedExpression; + } + /** * @return true if entry represents a removable duplicate entry of map. * @since 2021-05-22 @@ -1172,7 +1166,7 @@ public final class UnitDatabase { } return false; } - + /** * The units in this system, excluding prefixes. * @@ -1180,7 +1174,7 @@ public final class UnitDatabase { * @since v0.1.0 */ private final Map prefixlessUnits; - + /** * The unit prefixes in this system. * @@ -1188,7 +1182,7 @@ public final class UnitDatabase { * @since v0.1.0 */ private final Map prefixes; - + /** * The dimensions in this system. * @@ -1196,7 +1190,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map> dimensions; - + /** * A map mapping strings to units (including prefixes) * @@ -1204,7 +1198,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private final Map units; - + /** * A map mapping strings to unit sets * @@ -1212,7 +1206,7 @@ public final class UnitDatabase { * @since v1.0.0 */ private final Map> unitSets; - + /** * The rule that specifies when prefix repetition is allowed. It takes in one * argument: a list of the prefixes being applied to the unit @@ -1224,7 +1218,7 @@ public final class UnitDatabase { * {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))} */ private Predicate> prefixRepetitionRule; - + /** * A parser that can parse unit expressions. * @@ -1233,14 +1227,14 @@ public final class UnitDatabase { */ private final ExpressionParser unitExpressionParser = new ExpressionParser.Builder<>( this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) - .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2) - .build(); - + .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) + .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + .addBinaryOperator("space_times", (o1, o2) -> o1.times(o2), 2) + .addSpaceFunction("space_times") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 4) + .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 3).build(); + /** * A parser that can parse unit value expressions. * @@ -1249,15 +1243,16 @@ public final class UnitDatabase { */ private final ExpressionParser 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("|", (o1, o2) -> o1.dividedBy(o2), 3) - .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) + .addBinaryOperator("space_times", (o1, o2) -> o1.times(o2), 2) + .addSpaceFunction("space_times") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 4) + .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 3) + .build(); + /** * A parser that can parse unit prefix expressions * @@ -1266,15 +1261,15 @@ public final class UnitDatabase { */ private final ExpressionParser prefixExpressionParser = new ExpressionParser.Builder<>( 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(); - + .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. * @@ -1283,14 +1278,14 @@ public final class UnitDatabase { */ private final ExpressionParser> unitDimensionParser = new ExpressionParser.Builder<>( this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0) - .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) - .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2) - .addNumericOperator("^", (o1, o2) -> { - final int exponent = (int) Math.round(o2.value()); - return o1.toExponent(exponent); - }, 1).build(); - + .addSpaceFunction("*") + .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) + .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2) + .addNumericOperator("^", (o1, o2) -> { + final int exponent = (int) Math.round(o2.value()); + return o1.toExponent(exponent); + }, 1).build(); + /** * Creates the {@code UnitsDatabase}. * @@ -1300,7 +1295,7 @@ public final class UnitDatabase { public UnitDatabase() { this(prefixes -> true); } - + /** * Creates the {@code UnitsDatabase} * @@ -1320,7 +1315,7 @@ public final class UnitDatabase { .test(this.getPrefixesFromName(entry.getKey()))); this.unitSets = new HashMap<>(); } - + /** * Adds a unit dimension to the database. * @@ -1338,7 +1333,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. * @@ -1357,7 +1352,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1366,12 +1361,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("!")) { @@ -1382,7 +1377,7 @@ public final class UnitDatabase { this.addDimension(name, this.getDimensionFromExpression(expression)); } } - + /** * Adds a unit prefix to the database. * @@ -1399,7 +1394,7 @@ public final class UnitDatabase { this.prefixes.put(Objects.requireNonNull(name, "name must not be null."), namedPrefix); } - + /** * Adds a unit to the database. * @@ -1416,7 +1411,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. * @@ -1435,7 +1430,7 @@ public final class UnitDatabase { lineCounter); return; } - + // divide line into name and expression final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) @@ -1443,15 +1438,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("!")) { @@ -1472,7 +1467,7 @@ public final class UnitDatabase { } } } - + /** * Add a unit set to the database. * @@ -1490,10 +1485,10 @@ public final class UnitDatabase { "Unit sets must be all the same dimension, " + value + " is not."); } - + this.unitSets.put(name, value); } - + /** * Removes all units, unit sets, prefixes and dimensions from this database. * @@ -1506,7 +1501,7 @@ public final class UnitDatabase { this.prefixlessUnits.clear(); this.unitSets.clear(); } - + /** * Tests if the database has a unit dimension with this name. * @@ -1518,7 +1513,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. * @@ -1530,7 +1525,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 @@ -1543,7 +1538,7 @@ public final class UnitDatabase { public boolean containsUnitName(final String name) { return this.units.containsKey(name); } - + /** * Returns true iff there is a unit set with this name. * @@ -1556,7 +1551,7 @@ public final class UnitDatabase { public boolean containsUnitSetName(String name) { return this.unitSets.containsKey(name); } - + /** * @return a map mapping dimension names to dimensions * @since 2019-04-13 @@ -1565,7 +1560,7 @@ public final class UnitDatabase { public Map> dimensionMap() { return Collections.unmodifiableMap(this.dimensions); } - + /** * Evaluates a unit expression, following the same rules as * {@link #getUnitFromExpression}. @@ -1577,39 +1572,15 @@ 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 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 operation also breaks stuff like "1e-5" - for (int i = 0; i < modifiedExpression.length(); i++) { - if (modifiedExpression.charAt(i) == '-' - && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^') - .contains(modifiedExpression.charAt(i - 2)))) { - // found a broken negative number - modifiedExpression = modifiedExpression.substring(0, i + 1) - + modifiedExpression.substring(i + 2); - } - } - - return this.unitValueExpressionParser.parseExpression(modifiedExpression); + + return this.unitValueExpressionParser + .parseExpression(formatExpression(expression)); } - + /** * Gets a unit dimension from the database using its name. * @@ -1627,7 +1598,7 @@ public final class UnitDatabase { else return dimension; } - + /** * Uses the database's data to parse an expression into a unit dimension *

    @@ -1651,24 +1622,14 @@ public final class UnitDatabase { public ObjectProduct getDimensionFromExpression( final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + // attempt to get a dimension as an alias first if (this.containsDimensionName(expression)) return this.getDimension(expression); - - // force operators to have spaces - String modifiedExpression = expression; - - // format expression - for (final Entry replacement : EXPRESSION_REPLACEMENTS - .entrySet()) { - modifiedExpression = replacement.getKey().matcher(modifiedExpression) - .replaceAll(replacement.getValue()); - } - - return this.unitDimensionParser.parseExpression(modifiedExpression); + + return this.unitDimensionParser.parseExpression(formatExpression(expression)); } - + /** * Gets a unit. If it is linear, cast it to a LinearUnit and return it. * Otherwise, throw an {@code IllegalArgumentException}. @@ -1687,7 +1648,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( @@ -1696,7 +1657,7 @@ public final class UnitDatabase { } else { // get a linear unit final Unit unit = this.getUnit(name); - + if (unit instanceof LinearUnit) return (LinearUnit) unit; else @@ -1704,7 +1665,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. @@ -1723,7 +1684,7 @@ public final class UnitDatabase { return LinearUnitValue.getExact(this.getLinearUnit(name), 1); } } - + /** * Gets a unit prefix from the database from its name * @@ -1744,7 +1705,7 @@ public final class UnitDatabase { return prefix; } } - + /** * Gets all of the prefixes that are on a unit name, in application order. * @@ -1756,12 +1717,12 @@ public final class UnitDatabase { List getPrefixesFromName(final String unitName) { final List 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) @@ -1771,7 +1732,7 @@ public final class UnitDatabase { longestPrefixName = name.substring(0, longestLength); } } - + // longest prefix found! final UnitPrefix prefix = this.getPrefix(longestPrefixName); prefixes.add(0, prefix); @@ -1779,7 +1740,7 @@ public final class UnitDatabase { } return prefixes; } - + /** * Gets a unit prefix from a prefix expression *

    @@ -1796,24 +1757,14 @@ 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 replacement : EXPRESSION_REPLACEMENTS - .entrySet()) { - modifiedExpression = replacement.getKey().matcher(modifiedExpression) - .replaceAll(replacement.getValue()); - } - - return this.prefixExpressionParser.parseExpression(modifiedExpression); + + return this.prefixExpressionParser.parseExpression(formatExpression(expression)); } - + /** * @return the prefixRepetitionRule * @since 2020-08-26 @@ -1822,7 +1773,7 @@ public final class UnitDatabase { public final Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * Gets a unit from the database from its name, looking for prefixes. * @@ -1855,9 +1806,9 @@ public final class UnitDatabase { } else return unit; } - + } - + /** * Uses the database's unit data to parse an expression into a unit *

    @@ -1882,38 +1833,15 @@ 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 replacement : EXPRESSION_REPLACEMENTS - .entrySet()) { - modifiedExpression = replacement.getKey().matcher(modifiedExpression) - .replaceAll(replacement.getValue()); - } - - // the previous operation breaks negative numbers, fix them! - // (i.e. -2 becomes - 2) - for (int i = 0; i < modifiedExpression.length(); i++) { - if (modifiedExpression.charAt(i) == '-' - && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^') - .contains(modifiedExpression.charAt(i - 2)))) { - // found a broken negative number - modifiedExpression = modifiedExpression.substring(0, i + 1) - + modifiedExpression.substring(i + 2); - } - } - - return this.unitExpressionParser.parseExpression(modifiedExpression); + + return this.unitExpressionParser + .parseExpression(formatExpression(expression)); } - + /** * Get a unit set from its name, throwing a {@link NoSuchElementException} if * there is none. @@ -1930,7 +1858,7 @@ public final class UnitDatabase { throw new NoSuchElementException("No unit set with name " + name); return unitSet; } - + /** * Parses a semicolon-separated expression to get the unit set being used. * @@ -1942,7 +1870,7 @@ public final class UnitDatabase { final List units = new ArrayList<>(parts.length); for (final String unitName : parts) { final Unit unit = this.getUnitFromExpression(unitName.trim()); - + if (!(unit instanceof LinearUnit)) { throw new IllegalArgumentException(String.format( "Unit '%s' is in a unit-set expression, but is not linear.", @@ -1957,7 +1885,7 @@ public final class UnitDatabase { } return units; } - + /** * Adds all dimensions from a file, using data from the database to parse * them. @@ -2003,7 +1931,7 @@ public final class UnitDatabase { } return errors; } - + /** * Adds all dimensions from a {@code InputStream}. Otherwise, works like * {@link #loadDimensionFile}. @@ -2030,7 +1958,7 @@ public final class UnitDatabase { } return errors; } - + /** * Adds all units from a file, using data from the database to parse them. *

    @@ -2075,7 +2003,7 @@ public final class UnitDatabase { } return errors; } - + /** * Adds all units from a {@code InputStream}. Otherwise, works like * {@link #loadUnitsFile}. @@ -2101,7 +2029,7 @@ public final class UnitDatabase { } return errors; } - + /** * @param includeDuplicates if false, duplicates are removed from the map * @return a map mapping prefix names to prefixes @@ -2116,7 +2044,7 @@ public final class UnitDatabase { .conditionalExistenceMap(this.prefixes, entry -> !isRemovableDuplicate(this.prefixes, entry))); } - + /** * @param prefixRepetitionRule the prefixRepetitionRule to set * @since 2020-08-26 @@ -2126,7 +2054,7 @@ public final class UnitDatabase { Predicate> prefixRepetitionRule) { this.prefixRepetitionRule = prefixRepetitionRule; } - + /** * @return a string stating the number of units, prefixes and dimensions in * the database @@ -2138,7 +2066,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. *

    @@ -2170,7 +2098,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, @@ -2188,7 +2116,7 @@ public final class UnitDatabase { entry -> !isRemovableDuplicate(this.prefixlessUnits, entry))); } - + /** * @return an unmodifiable map mapping names to unit sets * @since 2024-08-16 diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 1c8df9f..051082d 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -578,7 +578,8 @@ public final class ExpressionParser { * @since 2019-03-17 * @since v0.2.0 */ - String convertExpressionToReversePolish(final String expression) { + // TODO revert to package private + public String convertExpressionToReversePolish(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); final List components = new ArrayList<>(); diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index c78837f..800d13d 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -190,14 +190,35 @@ class UnitDatabaseTest { } private static final Stream testEvaluateExpressionValid() { + UncertainDouble uncertainTwoThirds = UncertainDouble.of(2.0, 1.0) + .dividedBy(UncertainDouble.of(3.0, 1.0)); return Stream.of( Arguments.of("J + (2 * 3) J + (20 / 4) J", LinearUnitValue.of(J, UncertainDouble.of(12, Math.sqrt(14.625)))), + Arguments.of("J + 2 * 3 * J + 20 / 4 * J", + LinearUnitValue.of(J, + UncertainDouble.of(12, Math.sqrt(14.625)))), Arguments.of("J - -1 * J", LinearUnitValue.of(J, UncertainDouble.of(2, 1))), Arguments.of("K^2", - LinearUnitValue.of(K.times(K), UncertainDouble.of(1, 0)))); + LinearUnitValue.of(K.times(K), UncertainDouble.of(1, 0))), + Arguments.of("2 J / 3 J", + LinearUnitValue.of(J.dividedBy(J), uncertainTwoThirds))); + } + + private static final Stream testFormatExpression() { + return Stream.of( + Arguments.of("1*2", "1 * 2"), + Arguments.of("1/2", "1 / 2"), + Arguments.of("1|2", "1 | 2"), + Arguments.of("1^2", "1 ^ 2"), + Arguments.of("1 * 2", "1 * 2"), + Arguments.of("+1", "+1"), + Arguments.of("-1", "-1"), + Arguments.of("1.1e+5", "1.1e+5"), + Arguments.of("1.25e-5", "1.25e-5") + ); } /** @@ -253,6 +274,12 @@ class UnitDatabaseTest { final var actualU = database.getUnitFromExpression(expression); assertEquals(expectedU, actualU); } + + @ParameterizedTest + @MethodSource + public void testFormatExpression(String expression, String expected) { + assertEquals(expected, UnitDatabase.formatExpression(expression)); + } /** * Test for {@link UnitDatabase#getUnit}, {@link UnitDatabase#getLinearUnit} -- cgit v1.2.3 From ef34d9b0a1346ec6a6243dc4df7c59612faf6768 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Tue, 10 Jun 2025 12:37:50 -0500 Subject: Correct sample expressions in about text The expressions were valid, but used 'hr' instead of 'h' for the hour, which is not the abbreviation used by 7Units. --- src/main/resources/about/en.txt | 2 +- src/main/resources/about/fr.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/about/en.txt b/src/main/resources/about/en.txt index 068f922..37986e5 100644 --- a/src/main/resources/about/en.txt +++ b/src/main/resources/about/en.txt @@ -4,7 +4,7 @@ About 7Units Version [VERSION] 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". +like "10 m/s + (25^2 - 5^2) mi/h". This software was written by Adrien Hopkins . diff --git a/src/main/resources/about/fr.txt b/src/main/resources/about/fr.txt index d8d82aa..fc7ed83 100644 --- a/src/main/resources/about/fr.txt +++ b/src/main/resources/about/fr.txt @@ -4,7 +4,7 @@ inspiré par GNU Units (https://www.gnu.org/software/units/). Vous pouvez l’utiliser pour convertir des unités, mais vous pouvez aussi l’utiliser comme calculatrice, computer et convertir des expressions -comme "10 m/s + (25^2 - 5^2) mi/hr". +comme "10 m/s + (25^2 - 5^2) mi/h". Ce logiciel est par Adrien Hopkins . -- cgit v1.2.3 From 9025c4cfeb1f4e4d5d9b151a3797752f80a58bf6 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 15 Jun 2025 18:43:42 -0500 Subject: Revert expressionToRPN to package-private This resolves the last remaining TODO/FIXME comment --- .../java/sevenUnits/utils/ExpressionParser.java | 188 ++++++++++----------- 1 file changed, 93 insertions(+), 95 deletions(-) diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 051082d..783a135 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -56,7 +56,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final Function objectObtainer; - + /** * The function of the space as an operator (like 3 x y) * @@ -64,7 +64,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private String spaceFunction = null; - + /** * A map mapping operator strings to operator functions, for unary * operators. @@ -73,7 +73,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final Map> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. @@ -82,7 +82,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final Map> binaryOperators; - + /** * A map mapping operator strings to numeric functions. * @@ -90,7 +90,7 @@ public final class ExpressionParser { * @since v0.5.0 */ private final Map> numericOperators; - + /** * Creates the {@code Builder}. * @@ -107,7 +107,7 @@ public final class ExpressionParser { this.binaryOperators = new HashMap<>(); this.numericOperators = new HashMap<>(); } - + /** * Adds a binary operator to the builder. * @@ -125,7 +125,7 @@ public final class ExpressionParser { final BinaryOperator 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 priorityOperator = new PriorityBinaryOperator<>( @@ -134,12 +134,12 @@ public final class ExpressionParser { 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. @@ -155,7 +155,7 @@ public final class ExpressionParser { 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 priorityOperator = new PriorityBiFunction<>( @@ -164,12 +164,12 @@ public final class ExpressionParser { 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. @@ -181,15 +181,15 @@ public final class ExpressionParser { */ public Builder 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. * @@ -207,7 +207,7 @@ public final class ExpressionParser { final UnaryOperator 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 priorityOperator = new PriorityUnaryOperator<>( @@ -220,7 +220,7 @@ public final class ExpressionParser { this.unaryOperators.put(text, priorityOperator); return this; } - + /** * @return an {@code ExpressionParser} instance with the properties * given to this builder @@ -232,7 +232,7 @@ public final class ExpressionParser { this.binaryOperators, this.numericOperators, this.spaceFunction); } } - + /** * A binary operator with a priority field that determines which operators * apply first. @@ -242,8 +242,8 @@ public final class ExpressionParser { * @since 2019-03-17 * @since v0.2.0 */ - private static abstract class PriorityBinaryOperator - implements BinaryOperator, Comparable> { + private static abstract class PriorityBiFunction implements + BiFunction, Comparable> { /** * The operator's priority. Higher-priority operators are applied before * lower-priority operators @@ -252,7 +252,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final int priority; - + /** * Creates the {@code PriorityBinaryOperator}. * @@ -260,10 +260,10 @@ public final class ExpressionParser { * @since 2019-03-17 * @since v0.2.0 */ - public PriorityBinaryOperator(final int priority) { + public PriorityBiFunction(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. * @@ -275,7 +275,7 @@ public final class ExpressionParser { * @since v0.2.0 */ @Override - public int compareTo(final PriorityBinaryOperator o) { + public int compareTo(final PriorityBiFunction o) { if (this.priority < o.priority) return -1; else if (this.priority > o.priority) @@ -283,7 +283,7 @@ public final class ExpressionParser { else return 0; } - + /** * @return priority * @since 2019-03-22 @@ -293,7 +293,7 @@ public final class ExpressionParser { return this.priority; } } - + /** * A binary operator with a priority field that determines which operators * apply first. @@ -303,8 +303,8 @@ public final class ExpressionParser { * @since 2019-03-17 * @since v0.2.0 */ - private static abstract class PriorityBiFunction implements - BiFunction, Comparable> { + private static abstract class PriorityBinaryOperator + implements BinaryOperator, Comparable> { /** * The operator's priority. Higher-priority operators are applied before * lower-priority operators @@ -313,7 +313,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final int priority; - + /** * Creates the {@code PriorityBinaryOperator}. * @@ -321,10 +321,10 @@ public final class ExpressionParser { * @since 2019-03-17 * @since v0.2.0 */ - public PriorityBiFunction(final int priority) { + public PriorityBinaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. * @@ -336,7 +336,7 @@ public final class ExpressionParser { * @since v0.2.0 */ @Override - public int compareTo(final PriorityBiFunction o) { + public int compareTo(final PriorityBinaryOperator o) { if (this.priority < o.priority) return -1; else if (this.priority > o.priority) @@ -344,7 +344,7 @@ public final class ExpressionParser { else return 0; } - + /** * @return priority * @since 2019-03-22 @@ -354,7 +354,7 @@ public final class ExpressionParser { return this.priority; } } - + /** * A unary operator with a priority field that determines which operators * apply first. @@ -374,7 +374,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final int priority; - + /** * Creates the {@code PriorityUnaryOperator}. * @@ -385,7 +385,7 @@ public final class ExpressionParser { public PriorityUnaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. * @@ -405,7 +405,7 @@ public final class ExpressionParser { else return 0; } - + /** * @return priority * @since 2019-03-22 @@ -415,7 +415,7 @@ public final class ExpressionParser { return this.priority; } } - + /** * The types of tokens that are available. * @@ -426,7 +426,7 @@ public final class ExpressionParser { private static enum TokenType { OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR; } - + /** * The opening bracket. * @@ -434,7 +434,7 @@ public final class ExpressionParser { * @since v0.2.0 */ public static final char OPENING_BRACKET = '('; - + /** * The closing bracket. * @@ -442,7 +442,7 @@ public final class ExpressionParser { * @since v0.2.0 */ public static final char CLOSING_BRACKET = ')'; - + /** * Finds the other bracket in a pair of brackets, given the position of one. * @@ -456,9 +456,9 @@ public final class ExpressionParser { 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) { @@ -475,16 +475,16 @@ public final class ExpressionParser { 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) { @@ -493,10 +493,10 @@ public final class ExpressionParser { 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}. @@ -505,7 +505,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final Function objectObtainer; - + /** * A map mapping operator strings to operator functions, for unary operators. * @@ -513,7 +513,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final Map> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. @@ -522,7 +522,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final Map> binaryOperators; - + /** * A map mapping operator strings to numeric functions. * @@ -530,7 +530,7 @@ public final class ExpressionParser { * @since v0.5.0 */ private final Map> numericOperators; - + /** * The operator for space, or null if spaces have no function. * @@ -538,7 +538,7 @@ public final class ExpressionParser { * @since v0.2.0 */ private final String spaceOperator; - + /** * Creates the {@code ExpressionParser}. * @@ -561,7 +561,7 @@ public final class ExpressionParser { this.numericOperators = numericOperators; this.spaceOperator = spaceOperator; } - + /** * Converts a given mathematical expression to reverse Polish notation * (operators after operands). @@ -578,22 +578,21 @@ public final class ExpressionParser { * @since 2019-03-17 * @since v0.2.0 */ - // TODO revert to package private - public String convertExpressionToReversePolish(final String expression) { + String convertExpressionToReversePolish(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final List 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) != ' ') { @@ -623,15 +622,15 @@ public final class ExpressionParser { .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++) { @@ -641,7 +640,7 @@ public final class ExpressionParser { } } } - + // turn the expression into reverse Polish while (true) { final int highestPriorityOperatorPosition = this @@ -649,7 +648,7 @@ public final class ExpressionParser { if (highestPriorityOperatorPosition == -1) { break; } - + // swap components based on what kind of operator there is // 1 + 2 becomes 2 1 + // - 1 becomes 1 - @@ -684,16 +683,15 @@ public final class ExpressionParser { throw new AssertionError("Expected operator, found non-operator."); } } - + // join all of the components together, then ensure there is only one // space in a row - if (components.size() != 1) { + if (components.size() != 1) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\"."); - } return components.get(0).replaceAll(" +", " ").trim(); } - + /** * Finds the position of the highest-priority operator in a list * @@ -711,18 +709,18 @@ public final class ExpressionParser { // 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 unaryOperator = this.unaryOperators .get(components.get(i)); final int unaryPriority = unaryOperator.getPriority(); - + if (unaryPriority > maxPriority) { maxPriority = unaryPriority; maxPriorityPosition = i; @@ -732,7 +730,7 @@ public final class ExpressionParser { final PriorityBinaryOperator binaryOperator = this.binaryOperators .get(components.get(i)); final int binaryPriority = binaryOperator.getPriority(); - + if (binaryPriority > maxPriority) { maxPriority = binaryPriority; maxPriorityPosition = i; @@ -742,7 +740,7 @@ public final class ExpressionParser { final PriorityBiFunction numericOperator = this.numericOperators .get(components.get(i)); final int numericPriority = numericOperator.getPriority(); - + if (numericPriority > maxPriority) { maxPriority = numericPriority; maxPriorityPosition = i; @@ -752,11 +750,11 @@ public final class ExpressionParser { break; } } - + // max priority position found return maxPriorityPosition; } - + /** * Determines whether an inputted string is an object or an operator * @@ -768,7 +766,7 @@ public final class ExpressionParser { */ 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)) @@ -778,7 +776,7 @@ public final class ExpressionParser { else return TokenType.OBJECT; } - + /** * Parses an expression. * @@ -792,7 +790,7 @@ public final class ExpressionParser { return this.parseReversePolishExpression( this.convertExpressionToReversePolish(expression)); } - + /** * Parses an expression expressed in reverse Polish notation. * @@ -804,43 +802,43 @@ public final class ExpressionParser { */ T parseReversePolishExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final Deque stack = new ArrayDeque<>(); final Deque 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 binaryOperator = this.binaryOperators .get(item); - + stack.push(binaryOperator.apply(o2, o1)); 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 op = this.numericOperators .get(item); stack.push(op.apply(ot, on)); break; - + case OBJECT: // just add it to the stack // these try-catch statements are necessary @@ -849,41 +847,41 @@ public final class ExpressionParser { // 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) { + } catch (final Exception e) { try { doubleStack.push(UncertainDouble.fromString(item)); - } catch (IllegalArgumentException e2) { + } catch (final IllegalArgumentException e2) { try { doubleStack.push( UncertainDouble.of(Double.parseDouble(item), 0)); - } catch (NumberFormatException e3) { + } catch (final 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 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( -- cgit v1.2.3 From 255a0ac50b07d4fef9664767c4123ecaf4881d55 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 15 Jun 2025 18:44:18 -0500 Subject: Fix potential resource leaks in Presenter --- src/main/java/sevenUnitsGUI/Presenter.java | 492 +++++++++++++++-------------- 1 file changed, 250 insertions(+), 242 deletions(-) diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index ff7e23c..9913e89 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -86,12 +86,11 @@ public final class Presenter { * */ static final String DEFAULT_LOCALE = "en"; - + private static final List LOCAL_LOCALES = List.of("en", "fr"); private static final Path USER_LOCALES_DIR = userConfigDir() .resolve("SevenUnits").resolve("locales"); - - + /** * Adds default units and dimensions to a database. * @@ -113,14 +112,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); } - + private static String displayRuleToString( Function numberDisplayRule) { if (numberDisplayRule instanceof FixedDecimals) @@ -134,7 +133,7 @@ public final class Presenter { else return numberDisplayRule.toString(); } - + /** * Determines where to wrap {@code toWrap} with a max line length of * {@code maxLineLength}. If no good spot is found, returns -1. @@ -149,7 +148,7 @@ public final class Presenter { } return -1; } - + /** * Gets the text of a resource file as a set of strings (each one is one line * of the text). @@ -161,7 +160,7 @@ public final class Presenter { */ private static List getLinesFromResource(String filename) { final List lines = new ArrayList<>(); - + try (var stream = inputStream(filename); var scanner = new Scanner(stream)) { while (scanner.hasNextLine()) { @@ -171,10 +170,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. * @@ -186,7 +185,7 @@ public final class Presenter { private static InputStream inputStream(String filepath) { return Presenter.class.getResourceAsStream(filepath); } - + /** * Convert a linear unit value to a string, where the number is rounded to * the nearest integer. @@ -197,26 +196,26 @@ public final class Presenter { private static String linearUnitValueIntToString(LinearUnitValue uv) { return Long.toString(Math.round(uv.getValueExact())) + " " + uv.getUnit(); } - + private static Map.Entry parseSettingLine(String line) { final var equalsIndex = line.indexOf('='); if (equalsIndex == -1) throw new IllegalStateException( "Settings file is malformed at line: " + line); - + final var param = line.substring(0, equalsIndex); final var value = line.substring(equalsIndex + 1); - + return Map.entry(param, value); } - + /** Gets a Path from a pathname in the config file. */ private static Path pathFromConfig(String pathname) { return CONFIG_FILE.getParent().resolve(pathname); } - + // ====== SETTINGS ====== - + private static String searchRuleToString( Function, Map> searchRule) { if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) @@ -228,7 +227,7 @@ public final class Presenter { else return searchRule.toString(); } - + /** * @return true iff a and b have any elements in common * @since 2022-04-19 @@ -241,7 +240,7 @@ public final class Presenter { } return false; } - + private static Path userConfigDir() { if (System.getProperty("os.name").startsWith("Windows")) { final var envFolder = System.getenv("LOCALAPPDATA"); @@ -256,7 +255,7 @@ public final class Presenter { else return Path.of(envFolder); } - + /** * @return {@code line} with any comments removed. * @since 2021-03-13 @@ -266,7 +265,7 @@ public final class Presenter { final var index = line.indexOf('#'); return index == -1 ? line : line.substring(0, index); } - + /** * Wraps a string, ensuring no line is longer than {@code maxLineLength}. * @@ -291,23 +290,23 @@ public final class Presenter { wrapped.append(remaining); return wrapped.toString(); } - + /** * 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. Not implemented yet. */ private Function 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 @@ -315,41 +314,41 @@ public final class Presenter { */ private Function 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> 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> 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 metricExceptions; - + /** maps locale names (e.g. 'en') to key-text maps */ final Map> locales; - + /** name of locale in locales to use */ String userLocale; - + /** * 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. @@ -368,7 +367,7 @@ public final class Presenter { private final Set customDimensionFiles = new HashSet<>(); /** Custom exception datafiles that will be loaded by {@link #reloadData} */ private final Set customExceptionFiles = new HashSet<>(); - + /** * Creates a Presenter * @@ -380,78 +379,40 @@ public final class Presenter { this.view = view; this.database = new UnitDatabase(); this.metricExceptions = new HashSet<>(); - + this.locales = this.loadLocales(); this.userLocale = DEFAULT_LOCALE; - + // set default settings temporarily if (Files.exists(CONFIG_FILE)) { this.loadSettings(CONFIG_FILE); } - + this.reloadData(); - + // print out unit counts System.out.println(this.loadStatMsg()); } - /** - * Clears then reloads all unit, prefix, dimension and exception data. - */ - public void reloadData() { - this.database.clear(); - this.metricExceptions.clear(); - addDefaults(this.database); - - if (this.useDefaultDatafiles) { - this.loadDefaultData(); + private void addLocaleFile(Map> locales, + Path file) throws IOException { + final Map locale = new HashMap<>(); + final String fileName = file.getName(file.getNameCount() - 1).toString(); + final String localeName = fileName.substring(0, fileName.length() - 4); + try (Stream lines = Files.lines(file)) { + lines.forEach(line -> this.addLocaleLine(locale, line)); } - - this.customUnitFiles.forEach( - path -> this.handleLoadErrors(this.database.loadUnitsFile(path))); - this.customDimensionFiles.forEach( - path -> this.handleLoadErrors(this.database.loadDimensionFile(path))); - this.customExceptionFiles.forEach(this::loadExceptionFile); + locales.put(localeName, locale); } - /** - * Load units, prefixes and dimensions from the default files. - */ - private void loadDefaultData() { - // load units and prefixes - try (final var units = inputStream(DEFAULT_UNITS_FILEPATH)) { - this.handleLoadErrors(this.database.loadUnitsFromStream(units)); - } catch (final IOException e) { - throw new AssertionError("Loading of unitsfile.txt failed.", e); - } - - // load dimensions - try (final var dimensions = inputStream( - DEFAULT_DIMENSIONS_FILEPATH)) { - this.handleLoadErrors( - this.database.loadDimensionsFromStream(dimensions)); - } catch (final IOException e) { - throw new AssertionError("Loading of dimensionfile.txt failed.", e); - } - - // load metric exceptions - try { - try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); - var scanner = new Scanner(exceptions)) { - while (scanner.hasNextLine()) { - final var line = Presenter - .withoutComments(scanner.nextLine()); - if (!line.isBlank()) { - this.metricExceptions.add(line); - } - } - } - } catch (final IOException e) { - throw new AssertionError("Loading of metric_exceptions.txt failed.", - e); - } + private void addLocaleLine(Map locale, String line) { + final String[] parts = line.split("=", 2); + if (parts.length < 2) + return; + + locale.put(parts[0], parts[1]); } - + /** * Applies a search rule to an entry in a name-unit map. * @@ -472,7 +433,7 @@ public final class Presenter { } 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. @@ -489,10 +450,10 @@ public final class Presenter { throw new UnsupportedOperationException( "This function can only be called when the view is an ExpressionConversionView"); final var xcview = (ExpressionConversionView) this.view; - + final var fromExpression = xcview.getFromExpression(); final var toExpression = xcview.getToExpression(); - + // expressions must not be empty if (fromExpression.isEmpty()) { this.view.showErrorMessage("Parse Error", @@ -504,7 +465,7 @@ public final class Presenter { "Please enter a unit expression in the To: box."); return; } - + final Optional uc; if (this.database.containsUnitSetName(toExpression)) { uc = this.convertExpressionToNamedMultiUnit(fromExpression, @@ -515,10 +476,10 @@ public final class Presenter { } else { uc = this.convertExpressionToExpression(fromExpression, toExpression); } - + uc.ifPresent(xcview::showExpressionConversionOutput); } - + /** * Converts a unit expression to another expression. * @@ -547,7 +508,7 @@ public final class Presenter { "Could not recognize text in To entry: " + e.getMessage()); return Optional.empty(); } - + // convert and show output if (!from.getUnit().canConvertTo(to)) { this.view.showErrorMessage("Conversion Error", @@ -556,7 +517,7 @@ public final class Presenter { return Optional.empty(); } final UncertainDouble uncertainValue; - + // uncertainty is meaningless for non-linear units, so we will have // to erase uncertainty information for them if (to instanceof LinearUnit) { @@ -566,14 +527,13 @@ public final class Presenter { final var value = from.asUnitValue().convertTo(to).getValue(); uncertainValue = UncertainDouble.of(value, 0); } - - final var uc = UnitConversionRecord.valueOf( - fromExpression, toExpression, "", - this.numberDisplayRule.apply(uncertainValue)); + + final var uc = UnitConversionRecord.valueOf(fromExpression, toExpression, + "", this.numberDisplayRule.apply(uncertainValue)); return Optional.of(uc); - + } - + /** * Convert an expression to a MultiUnit. If an error happened, it is shown to * the view and Optional.empty() is returned. @@ -591,7 +551,7 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - + final List toUnits = new ArrayList<>(toExpressions.length); for (final String toExpression : toExpressions) { try { @@ -610,7 +570,7 @@ public final class Presenter { return Optional.empty(); } } - + final List toValues; try { toValues = from.convertToMultiple(toUnits); @@ -619,12 +579,12 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Convert an expression to a MultiUnit with a name from the database. If an * error happened, it is shown to the view and Optional.empty() is returned. @@ -642,7 +602,7 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - + final var toUnits = this.database.getUnitSet(toName); final List toValues; try { @@ -652,12 +612,12 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Converts from the view's input unit to its output unit. Displays an error * message if any of the required fields are invalid. @@ -674,11 +634,11 @@ public final class Presenter { throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); final var ucview = (UnitConversionView) this.view; - + final var fromUnitOptional = ucview.getFromSelection(); final var toUnitOptional = ucview.getToSelection(); final var inputValueString = ucview.getInputValue(); - + // extract values from optionals final String fromUnitString, toUnitString; if (fromUnitOptional.isPresent()) { @@ -695,11 +655,11 @@ public final class Presenter { "Please specify a To unit"); return; } - + // convert strings to data, checking if anything is invalid final Unit fromUnit; final UncertainDouble uncertainValue; - + if (this.database.containsUnitName(fromUnitString)) { fromUnit = this.database.getUnit(fromUnitString); } else @@ -717,14 +677,13 @@ public final class Presenter { this.convertUnitToUnit(fromUnitString, toUnitString, inputValueString, fromUnit, toUnit, uncertainValue)); } else if (this.database.containsUnitSetName(toUnitString)) { - final var toMulti = this.database - .getUnitSet(toUnitString); + final var toMulti = this.database.getUnitSet(toUnitString); ucview.showUnitConversionOutput(this.convertUnitToMulti(fromUnitString, inputValueString, fromUnit, toMulti, uncertainValue)); } else throw this.viewError("Nonexistent To unit: %s", toUnitString); } - + private UnitConversionRecord convertUnitToMulti(String fromUnitString, String inputValueString, Unit fromUnit, List toMulti, UncertainDouble uncertainValue) { @@ -733,7 +692,7 @@ public final class Presenter { throw this.viewError("Could not convert between %s and %s", fromUnit, toUnit); } - + final LinearUnitValue initValue; if (fromUnit instanceof LinearUnit) { final var fromLinear = (LinearUnit) fromUnit; @@ -742,22 +701,21 @@ public final class Presenter { initValue = UnitValue.of(fromUnit, uncertainValue.value()) .convertToBase(NameSymbol.EMPTY); } - - final var converted = initValue - .convertToMultiple(toMulti); + + final var converted = initValue.convertToMultiple(toMulti); final var toExpression = this.linearUnitValueSumToString(converted); return UnitConversionRecord.valueOf(fromUnitString, toExpression, inputValueString, ""); - + } - + private UnitConversionRecord convertUnitToUnit(String fromUnitString, String toUnitString, String inputValueString, Unit fromUnit, Unit toUnit, UncertainDouble uncertainValue) { 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; @@ -767,21 +725,21 @@ public final class Presenter { final var initialValue = LinearUnitValue.of(fromLinear, uncertainValue); final var converted = initialValue.convertTo(toLinear); - + outputValueString = this.numberDisplayRule.apply(converted.getValue()); } else { final var initialValue = UnitValue.of(fromUnit, uncertainValue.value()); final var converted = initialValue.convertTo(toUnit); - + outputValueString = this.numberDisplayRule .apply(UncertainDouble.of(converted.getValue(), 0)); } - + return UnitConversionRecord.valueOf(fromUnitString, toUnitString, inputValueString, outputValueString); } - + /** * @return true iff duplicate units are shown in unit lists * @since 2022-03-30 @@ -790,39 +748,44 @@ public final class Presenter { public boolean duplicatesShown() { return this.showDuplicates; } - + + private String formatAboutText(Stream rawLines) { + return rawLines.map(Presenter::withoutComments) + .collect(Collectors.joining("\n")) + .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) + .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); + } + /** * @return text in About file * @since 2022-02-19 * @since v0.4.0 */ public String getAboutText() { - final Path customFilepath = Presenter.pathFromConfig( - "about/" + this.userLocale + ".txt"); + final Path customFilepath = Presenter + .pathFromConfig("about/" + this.userLocale + ".txt"); if (Files.exists(customFilepath)) { - try { - return formatAboutText(Files.lines(customFilepath)); - } catch (IOException e) { - final String filename = String.format("/about/%s.txt", this.userLocale); - return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + try (Stream lines = Files.lines(customFilepath)) { + return this.formatAboutText(lines); + } catch (final IOException e) { + final String filename = String.format("/about/%s.txt", + this.userLocale); + return this.formatAboutText( + Presenter.getLinesFromResource(filename).stream()); } } else if (LOCAL_LOCALES.contains(this.userLocale)) { - final String filename = String.format("/about/%s.txt", this.userLocale); - return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + final String filename = String.format("/about/%s.txt", + this.userLocale); + return this.formatAboutText( + Presenter.getLinesFromResource(filename).stream()); } else { final String filename = String.format("/about/%s.txt", DEFAULT_LOCALE); - return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + return this.formatAboutText( + Presenter.getLinesFromResource(filename).stream()); } } - private String formatAboutText(Stream rawLines) { - return rawLines - .map(Presenter::withoutComments).collect(Collectors.joining("\n")) - .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) - .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); - } - /** * @return set of all locales available to select * @since 2025-02-21 @@ -831,7 +794,7 @@ public final class Presenter { public Set getAvailableLocales() { return this.locales.keySet(); } - + /** * Gets a name for this dimension using the database * @@ -847,7 +810,7 @@ public final class Presenter { .filter(d -> d.equals(dimension)).findAny().map(Nameable::getName) .orElse(dimension.toString(Nameable::getName)); } - + /** * Gets the correct text for a provided ID. If this text is available in the * user's locale, that text is provided. Otherwise, text is taken from the @@ -861,7 +824,7 @@ public final class Presenter { final var defaultLocale = this.locales.get(DEFAULT_LOCALE); return userLocale.getOrDefault(textID, defaultLocale.get(textID)); } - + /** * @return the rule that is used by this presenter to convert numbers into * strings @@ -871,7 +834,7 @@ public final class Presenter { public Function getNumberDisplayRule() { return this.numberDisplayRule; } - + /** * @return the rule that is used by this presenter to convert strings into * numbers @@ -882,7 +845,7 @@ public final class Presenter { private Function getNumberParsingRule() { return this.numberParsingRule; } - + /** * @return the rule that determines whether a set of prefixes is valid * @since 2022-04-19 @@ -891,7 +854,7 @@ public final class Presenter { public Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * @return the rule that determines which units are prefixed * @since 2022-07-08 @@ -900,7 +863,7 @@ public final class Presenter { public Function, Map> getSearchRule() { return this.searchRule; } - + /** * @return a search rule that shows all single prefixes * @since 2022-07-08 @@ -910,16 +873,16 @@ public final class Presenter { return PrefixSearchRule.getCoherentOnlyRule( new HashSet<>(this.database.prefixMap(true).values())); } - + /** * @return user's selected locale * @since 2025-02-21 * @since v1.0.0 */ public String getUserLocale() { - return userLocale; + return this.userLocale; } - + /** * @return the view associated with this presenter * @since 2022-04-19 @@ -928,7 +891,7 @@ public final class Presenter { public View getView() { return this.view; } - + /** * Accepts a list of errors. If that list is non-empty, prints an error * message and alerts the user. @@ -947,7 +910,7 @@ public final class Presenter { errorMessage); } } - + /** * @return whether or not the provided unit is semi-metric (i.e. an * exception) @@ -964,7 +927,7 @@ public final class Presenter { && this.metricExceptions.contains(symbol.orElseThrow()) || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } - + /** * Convert a list of LinearUnitValues that you would get from a unit-set * conversion to a string. All but the last have their numbers rendered as @@ -982,7 +945,43 @@ public final class Presenter { return integerPart + " + " + this.numberDisplayRule.apply(last.getValue()) + " " + last.getUnit(); } - + + /** + * Load units, prefixes and dimensions from the default files. + */ + private void loadDefaultData() { + // load units and prefixes + try (final var units = inputStream(DEFAULT_UNITS_FILEPATH)) { + this.handleLoadErrors(this.database.loadUnitsFromStream(units)); + } catch (final IOException e) { + throw new AssertionError("Loading of unitsfile.txt failed.", e); + } + + // load dimensions + try (final var dimensions = inputStream(DEFAULT_DIMENSIONS_FILEPATH)) { + this.handleLoadErrors( + this.database.loadDimensionsFromStream(dimensions)); + } catch (final IOException e) { + throw new AssertionError("Loading of dimensionfile.txt failed.", e); + } + + // load metric exceptions + try { + try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); + var scanner = new Scanner(exceptions)) { + while (scanner.hasNextLine()) { + final var line = Presenter.withoutComments(scanner.nextLine()); + if (!line.isBlank()) { + this.metricExceptions.add(line); + } + } + } + } catch (final IOException e) { + throw new AssertionError("Loading of metric_exceptions.txt failed.", + e); + } + } + private void loadExceptionFile(Path exceptionFile) { try (var lines = Files.lines(exceptionFile)) { lines.map(Presenter::withoutComments) @@ -993,54 +992,38 @@ public final class Presenter { + exceptionFile + "\": " + e.getLocalizedMessage()); } } - + /** * Loads all available locales, including custom ones, into a map. + * * @return map containing locales */ private Map> loadLocales() { final Map> locales = new HashMap<>(); for (final String localeName : LOCAL_LOCALES) { final Map locale = new HashMap<>(); - String filename = "/locales/" + localeName + ".txt"; - getLinesFromResource(filename).forEach(line -> addLocaleLine(locale, line)); - locales.put(localeName, locale); + final String filename = "/locales/" + localeName + ".txt"; + getLinesFromResource(filename) + .forEach(line -> this.addLocaleLine(locale, line)); + locales.put(localeName, locale); } if (Files.exists(USER_LOCALES_DIR)) { - try { - Files.list(USER_LOCALES_DIR).forEach( - localeFile -> { - try { - addLocaleFile(locales, localeFile); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } catch (IOException e) { + try (Stream files = Files.list(USER_LOCALES_DIR)) { + files.forEach(localeFile -> { + try { + this.addLocaleFile(locales, localeFile); + } catch (final IOException e) { + e.printStackTrace(); + } + }); + } catch (final IOException e) { e.printStackTrace(); } } return locales; } - private void addLocaleFile(Map> locales, Path file) throws IOException { - final Map locale = new HashMap<>(); - String fileName = file.getName(file.getNameCount()-1).toString(); - String localeName = fileName.substring(0, fileName.length()-4); - Files.lines(file).forEach(line -> addLocaleLine(locale, line)); - locales.put(localeName, locale); - } - - private void addLocaleLine(Map locale, String line) { - String[] parts = line.split("=", 2); - if (parts.length < 2) { - return; - } - - locale.put(parts[0], parts[1]); - } - /** * Loads settings from the user's settings file and applies them to the * presenter. @@ -1053,11 +1036,11 @@ public final class Presenter { this.customDimensionFiles.clear(); this.customExceptionFiles.clear(); this.customUnitFiles.clear(); - + for (final Map.Entry setting : this .settingsFromFile(settingsFile)) { final var value = setting.getValue(); - + switch (setting.getKey()) { // set manually to avoid the unnecessary saving of the non-manual // methods @@ -1094,11 +1077,11 @@ public final class Presenter { if (this.locales.containsKey(value)) { this.userLocale = value; } else { - System.err.printf( - "Warning: unrecognized locale \"%s\".%n", value); + System.err.printf("Warning: unrecognized locale \"%s\".%n", + value); this.view.showErrorMessage("Unrecognized Locale", "Could not find locale \"" + value - + "\", resetting to default."); + + "\", resetting to default."); } break; default: @@ -1107,12 +1090,12 @@ public final class Presenter { break; } } - + if (this.view.getPresenter() != null) { this.updateView(); } } - + /** * @return a message showing how much stuff has been loaded * @since 2024-08-22 @@ -1120,30 +1103,36 @@ public final class Presenter { */ private String loadStatMsg() { return this.getLocalizedText("load_stat_msg") - .replace("[u]", Integer.toString( - this.database.unitMapPrefixless(false).size())) - .replace("[un]", Integer.toString( - this.database.unitMapPrefixless(true).size())) - .replace("[b]", Long.toString(this.database.unitMapPrefixless(false) - .values().stream().filter(IS_FULL_BASE).count())) - .replace("[p]", Integer.toString(this.database.prefixMap(false).size())) - .replace("[pn]", Integer.toString(this.database.prefixMap(true).size())) + .replace("[u]", + Integer.toString( + this.database.unitMapPrefixless(false).size())) + .replace("[un]", + Integer + .toString(this.database.unitMapPrefixless(true).size())) + .replace("[b]", + Long.toString(this.database.unitMapPrefixless(false).values() + .stream().filter(IS_FULL_BASE).count())) + .replace("[p]", + Integer.toString(this.database.prefixMap(false).size())) + .replace("[pn]", + Integer.toString(this.database.prefixMap(true).size())) .replace("[s]", Integer.toString(this.database.unitSetMap().size())) - .replace("[d]", Integer.toString(this.database.dimensionMap().size())); + .replace("[d]", + Integer.toString(this.database.dimensionMap().size())); } - + /** * @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 * unit list and imperial/USC units removed from the To unit list) - * + * * @since 2022-03-30 * @since v0.4.0 */ 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 @@ -1158,14 +1147,13 @@ public final class Presenter { final var ucview = (UnitConversionView) this.view; ucview.setDimensionNames(this.database.dimensionMap().keySet()); } - + this.updateView(); this.view.updateText(); } - + void prefixSelected() { - final var selectedPrefixName = this.view - .getViewedPrefixName(); + final var selectedPrefixName = this.view.getViewedPrefixName(); final Optional selectedPrefix = selectedPrefixName .map(name -> this.database.containsPrefixName(name) ? this.database.getPrefix(name) @@ -1174,7 +1162,26 @@ public final class Presenter { .ifPresent(prefix -> this.view.showPrefix(prefix.getNameSymbol(), String.valueOf(prefix.getMultiplier()))); } - + + /** + * Clears then reloads all unit, prefix, dimension and exception data. + */ + public void reloadData() { + this.database.clear(); + this.metricExceptions.clear(); + addDefaults(this.database); + + if (this.useDefaultDatafiles) { + this.loadDefaultData(); + } + + this.customUnitFiles.forEach( + path -> this.handleLoadErrors(this.database.loadUnitsFile(path))); + this.customDimensionFiles.forEach(path -> this + .handleLoadErrors(this.database.loadDimensionFile(path))); + this.customExceptionFiles.forEach(this::loadExceptionFile); + } + /** * Saves the presenter's current settings to the config file, creating it if * it doesn't exist. @@ -1192,10 +1199,10 @@ public final class Presenter { return false; } } - + return this.writeSettings(CONFIG_FILE); } - + private void setDisplayRuleFromString(String ruleString) { final var tokens = ruleString.split(" "); switch (tokens[0]) { @@ -1216,7 +1223,7 @@ public final class Presenter { break; } } - + /** * @param numberDisplayRule the new rule that will be used by this presenter * to convert numbers into strings @@ -1227,7 +1234,7 @@ public final class Presenter { Function numberDisplayRule) { this.numberDisplayRule = numberDisplayRule; } - + /** * @param numberParsingRule the new rule that will be used by this presenter * to convert strings into numbers @@ -1239,7 +1246,7 @@ public final class Presenter { Function numberParsingRule) { this.numberParsingRule = numberParsingRule; } - + /** * @param oneWayConversionEnabled whether not one-way conversion should be * enabled @@ -1251,7 +1258,7 @@ public final class Presenter { this.oneWayConversionEnabled = oneWayConversionEnabled; this.updateView(); } - + /** * @param prefixRepetitionRule the rule that determines whether a set of * prefixes is valid @@ -1263,7 +1270,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 @@ -1276,7 +1283,7 @@ public final class Presenter { Function, Map> searchRule) { this.searchRule = searchRule; } - + private void setSearchRuleFromString(String ruleString) { switch (ruleString) { case "NO_PREFIXES": @@ -1294,7 +1301,7 @@ public final class Presenter { ruleString); } } - + /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 @@ -1304,7 +1311,7 @@ public final class Presenter { this.showDuplicates = showDuplicateUnits; this.updateView(); } - + private List> settingsFromFile(Path settingsFile) { try (var lines = Files.lines(settingsFile)) { return lines.map(Presenter::withoutComments) @@ -1318,17 +1325,18 @@ public final class Presenter { } /** - * Sets whether or not the default datafiles will be loaded. - * This method automatically updates the view's units. + * Sets whether or not the default datafiles will be loaded. This method + * automatically updates the view's units. * - * @param useDefaultDatafiles whether or not default datafiles should be loaded + * @param useDefaultDatafiles whether or not default datafiles should be + * loaded */ public void setUseDefaultDatafiles(boolean useDefaultDatafiles) { this.useDefaultDatafiles = useDefaultDatafiles; this.reloadData(); this.updateView(); } - + /** * Sets the user's locale, updating the view. * @@ -1338,7 +1346,7 @@ public final class Presenter { this.userLocale = userLocale; this.view.updateText(); } - + /** * Shows a unit in the unit viewer * @@ -1355,7 +1363,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. @@ -1372,7 +1380,7 @@ public final class Presenter { : null); selectedUnit.ifPresent(this::showUnit); } - + /** * Updates the view's From and To units, if it has some * @@ -1383,20 +1391,20 @@ public final class Presenter { if (this.view instanceof UnitConversionView) { final var 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(); var unitSets = this.database.unitSetMap().entrySet().stream(); - + // filter by dimension, if one is selected if (selectedDimensionName.isPresent()) { final var viewDimension = this.database @@ -1408,7 +1416,7 @@ public final class Presenter { unitSets = unitSets.filter(us -> viewDimension .equals(us.getValue().get(0).getDimension())); } - + // filter by unit type, if desired if (this.oneWayConversionEnabled) { fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(), @@ -1419,7 +1427,7 @@ public final class Presenter { unitSets = unitSets .filter(us -> this.metricExceptions.contains(us.getKey())); } - + // set unit names ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule) .map(Map.Entry::getKey).collect(Collectors.toSet())); @@ -1438,7 +1446,7 @@ public final class Presenter { public boolean usingDefaultDatafiles() { return this.useDefaultDatafiles; } - + /** * @param message message to add * @param args string formatting arguments for message @@ -1451,7 +1459,7 @@ public final class Presenter { return new AssertionError("View Programming Error (from " + this.view + "): " + String.format(message, args)); } - + /** * Saves the presenter's settings to the user settings file. * @@ -1471,8 +1479,8 @@ public final class Presenter { String.format("include_duplicates=%s\n", this.showDuplicates)); writer.write(String.format("search_prefix_rule=%s\n", searchRuleToString(this.searchRule))); - writer.write( - String.format("use_default_datafiles=%s\n", this.useDefaultDatafiles)); + writer.write(String.format("use_default_datafiles=%s\n", + this.useDefaultDatafiles)); writer.write(String.format("locale=%s\n", this.userLocale)); return true; } catch (final IOException e) { -- cgit v1.2.3 From 34f0321a31e728fa66057d0decd9a938d133d596 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 15 Jun 2025 19:15:04 -0500 Subject: Update changelog for release 1.0.0 --- CHANGELOG.org | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.org b/CHANGELOG.org index 0798904..01b3b34 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -1,6 +1,7 @@ * Changelog All notable changes in this project will be shown in this file. -** Unreleased +** v1.0.0 - [2025-06-15 Sun] +This version is the first one with a stable public API; as per Semantic Versioning all backwards incompatible changes will be put on a major version. *** Added - *Allowed conversion to a sum of units (e.g. 4/3 ft \rightarrow 1 ft + 4 in).* - *Allowed exponents on units to be non-integer numbers.* @@ -9,10 +10,14 @@ All notable changes in this project will be shown in this file. This does not affect the names of units, prefixes and dimensions. - Added more information to the loading-success message, and added it to the about tab. - Added the ability to not use the default data files. +- Added the ability to generate Javadoc automatically. *** Changed - *Errors in unit/dimension files are shown in popups, rather than crashing the program.* +- Significantly increased number and coverage of automated tests (~./gradlew test~). *** Fixed - Fixed encoding of \pm character in values with uncertainty. +- ExpressionParser uses the correct order internally. + /Note: this failure was only visible if you call the submethods; the public method fixed the problem on its own. This was fixed primarily to improve testing./ ** v0.5.0 - [2024-03-24 Sun] *** Added - *Added specifications for all types data files used by 7Units.* -- cgit v1.2.3 From da740edd3972fa049c4c8d0e43448c10a6a65dce Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 15 Jun 2025 19:26:12 -0500 Subject: Format & clean up source code --- src/main/java/sevenUnits/ProgramInfo.java | 8 +- src/main/java/sevenUnits/package-info.java | 2 +- src/main/java/sevenUnits/unit/BaseDimension.java | 14 +- src/main/java/sevenUnits/unit/BaseUnit.java | 16 +- src/main/java/sevenUnits/unit/BritishImperial.java | 14 +- src/main/java/sevenUnits/unit/FunctionalUnit.java | 14 +- src/main/java/sevenUnits/unit/LinearUnit.java | 141 +++--- src/main/java/sevenUnits/unit/LinearUnitValue.java | 96 ++-- .../java/sevenUnits/unit/LoadingException.java | 32 +- src/main/java/sevenUnits/unit/Metric.java | 12 +- src/main/java/sevenUnits/unit/USCustomary.java | 10 +- src/main/java/sevenUnits/unit/Unit.java | 61 ++- src/main/java/sevenUnits/unit/UnitDatabase.java | 496 ++++++++++----------- src/main/java/sevenUnits/unit/UnitPrefix.java | 88 ++-- src/main/java/sevenUnits/unit/UnitType.java | 5 +- src/main/java/sevenUnits/unit/UnitValue.java | 36 +- src/main/java/sevenUnits/unit/package-info.java | 2 +- .../utils/ConditionalExistenceCollections.java | 63 ++- .../java/sevenUnits/utils/DecimalComparison.java | 38 +- .../java/sevenUnits/utils/ExpressionParser.java | 334 +++++++------- src/main/java/sevenUnits/utils/NameSymbol.java | 74 ++- src/main/java/sevenUnits/utils/Nameable.java | 4 +- src/main/java/sevenUnits/utils/ObjectProduct.java | 102 +++-- .../sevenUnits/utils/SemanticVersionNumber.java | 115 +++-- .../java/sevenUnits/utils/UncertainDouble.java | 173 ++++--- src/main/java/sevenUnits/utils/package-info.java | 2 +- .../sevenUnitsGUI/DefaultPrefixRepetitionRule.java | 8 +- src/main/java/sevenUnitsGUI/DelegateListModel.java | 30 +- .../sevenUnitsGUI/ExpressionConversionView.java | 4 +- src/main/java/sevenUnitsGUI/FilterComparator.java | 23 +- src/main/java/sevenUnitsGUI/GridBagBuilder.java | 25 +- src/main/java/sevenUnitsGUI/PrefixSearchRule.java | 19 +- src/main/java/sevenUnitsGUI/Presenter.java | 345 +++++++------- src/main/java/sevenUnitsGUI/SearchBoxList.java | 47 +- .../java/sevenUnitsGUI/StandardDisplayRules.java | 24 +- src/main/java/sevenUnitsGUI/TabbedView.java | 378 ++++++++-------- .../java/sevenUnitsGUI/UnitConversionRecord.java | 42 +- .../java/sevenUnitsGUI/UnitConversionView.java | 4 +- src/main/java/sevenUnitsGUI/View.java | 6 +- src/main/java/sevenUnitsGUI/ViewBot.java | 23 +- src/main/java/sevenUnitsGUI/package-info.java | 2 +- .../java/sevenUnits/unit/UnitDatabaseTest.java | 45 +- src/test/java/sevenUnits/unit/UnitTest.java | 106 ++--- src/test/java/sevenUnits/unit/UnitValueTest.java | 2 +- .../utils/ConditionalExistenceCollectionsTest.java | 57 +-- .../sevenUnits/utils/ExpressionParserTest.java | 80 ++-- src/test/java/sevenUnits/utils/NameSymbolTest.java | 76 ++-- .../java/sevenUnits/utils/ObjectProductTest.java | 8 +- .../java/sevenUnits/utils/SemanticVersionTest.java | 74 ++- .../java/sevenUnits/utils/UncertainDoubleTest.java | 44 +- src/test/java/sevenUnitsGUI/I18nTest.java | 37 +- .../java/sevenUnitsGUI/PrefixRepetitionTest.java | 8 +- src/test/java/sevenUnitsGUI/PrefixSearchTest.java | 16 +- src/test/java/sevenUnitsGUI/RoundingTest.java | 10 +- src/test/java/sevenUnitsGUI/TabbedViewTest.java | 28 +- 55 files changed, 1680 insertions(+), 1843 deletions(-) diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index 9b9832e..5ed1309 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -20,20 +20,20 @@ import sevenUnits.utils.SemanticVersionNumber; /** * Information about 7Units - * + * * @since 2021-06-28 * @since v0.3.1 */ public final class ProgramInfo { - + /** The version number (1.0.0-beta.2) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber .preRelease(1, 0, 0, "beta", 2); - + 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/package-info.java b/src/main/java/sevenUnits/package-info.java index b90a5ea..dc27e1f 100644 --- a/src/main/java/sevenUnits/package-info.java +++ b/src/main/java/sevenUnits/package-info.java @@ -16,7 +16,7 @@ */ /** * A program that converts units. - * + * * @author Adrien Hopkins * @version v0.3.0 * @since 2019-01-25 diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java index fe7b772..11b822e 100644 --- a/src/main/java/sevenUnits/unit/BaseDimension.java +++ b/src/main/java/sevenUnits/unit/BaseDimension.java @@ -23,7 +23,7 @@ import sevenUnits.utils.Nameable; /** * A dimension that defines a {@code BaseUnit} - * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.3.0 @@ -31,7 +31,7 @@ import sevenUnits.utils.Nameable; public final class BaseDimension implements Nameable { /** * Gets a {@code BaseDimension} with the provided name and symbol. - * + * * @param name name of dimension * @param symbol symbol used for dimension * @return dimension @@ -42,9 +42,7 @@ public final class BaseDimension implements Nameable { return new BaseDimension(name, symbol); } - /** - * The name of the dimension. - */ + /** The name of the dimension. */ private final String name; /** * The symbol used by the dimension. Symbols should be short, generally one @@ -54,7 +52,7 @@ public final class BaseDimension implements Nameable { /** * Creates the {@code BaseDimension}. - * + * * @param name name of unit * @param symbol symbol of unit * @throws NullPointerException if any argument is null @@ -66,9 +64,7 @@ public final class BaseDimension implements Nameable { this.symbol = Objects.requireNonNull(symbol, "symbol must not be null."); } - /** - * @since v0.4.0 - */ + /** @since v0.4.0 */ @Override public NameSymbol getNameSymbol() { return NameSymbol.of(this.name, this.symbol); diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java index 2898de5..13e76d9 100644 --- a/src/main/java/sevenUnits/unit/BaseUnit.java +++ b/src/main/java/sevenUnits/unit/BaseUnit.java @@ -28,7 +28,7 @@ import sevenUnits.utils.NameSymbol; * Note that BaseUnits must have names and symbols. This is because they * are used for toString code. Therefore, the Optionals provided by * {@link #getPrimaryName} and {@link #getSymbol} will always contain a value. - * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.3.0 @@ -36,7 +36,7 @@ import sevenUnits.utils.NameSymbol; public final class BaseUnit extends Unit { /** * Gets a base unit from the dimension it measures, its name and its symbol. - * + * * @param dimension dimension measured by this unit * @param name name of unit * @param symbol symbol of unit @@ -51,7 +51,7 @@ public final class BaseUnit extends Unit { /** * Gets a base unit from the dimension it measures, its name and its symbol. - * + * * @param dimension dimension measured by this unit * @param name name of unit * @param symbol symbol of unit @@ -65,14 +65,12 @@ public final class BaseUnit extends Unit { return new BaseUnit(dimension, name, symbol, otherNames); } - /** - * The dimension measured by this base unit. - */ + /** The dimension measured by this base unit. */ private final BaseDimension dimension; /** * Creates the {@code BaseUnit}. - * + * * @param dimension dimension of unit * @param primaryName name of unit * @param symbol symbol of unit @@ -91,7 +89,7 @@ public final class BaseUnit extends Unit { * Returns a {@code LinearUnit} with this unit as a base and a conversion * factor of 1. This operation must be done in order to allow units to be * created with operations. - * + * * @return this unit as a {@code LinearUnit} * @since 2019-10-16 * @since v0.3.0 @@ -115,7 +113,7 @@ public final class BaseUnit extends Unit { * @since 2019-10-16 * @since v0.3.0 */ - public final BaseDimension getBaseDimension() { + public BaseDimension getBaseDimension() { return this.dimension; } diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index a6fd43f..408e9e8 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -20,7 +20,7 @@ import sevenUnits.utils.NameSymbol; /** * A static utility class that contains units in the British Imperial system. - * + * * @author Adrien Hopkins * @since 2019-10-21 * @since v0.3.0 @@ -31,7 +31,7 @@ import sevenUnits.utils.NameSymbol; public final class BritishImperial { /** * Imperial units that measure area - * + * * @author Adrien Hopkins * @since 2019-11-08 * @since v0.3.0 @@ -47,7 +47,7 @@ public final class BritishImperial { /** * Imperial units that measure length - * + * * @author Adrien Hopkins * @since 2019-10-28 * @since v0.3.0 @@ -70,7 +70,9 @@ public final class BritishImperial { /** A league, equal to 3 miles. */ public static final LinearUnit LEAGUE = MILE.times(3); - /** A nautical mile, around 1 arcminute around the Earth's circumference. */ + /** + * A nautical mile, around 1 arcminute around the Earth's circumference. + */ 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); @@ -81,7 +83,7 @@ public final class BritishImperial { /** * British Imperial units that measure mass. - * + * * @author Adrien Hopkins * @since 2019-11-08 * @since v0.3.0 @@ -100,7 +102,7 @@ public final class BritishImperial { /** * British Imperial units that measure volume - * + * * @author Adrien Hopkins * @since 2019-11-08 * @since v0.3.0 diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index 41db164..1d55b42 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -24,7 +24,7 @@ import sevenUnits.utils.ObjectProduct; /** * A unit that uses functional objects to convert to and from its base. - * + * * @author Adrien Hopkins * @since 2019-05-22 * @since v0.3.0 @@ -33,7 +33,7 @@ final class FunctionalUnit extends Unit { /** * A function that accepts a value expressed in the unit's base and returns * that value expressed in this unit. - * + * * @since 2019-05-22 * @since v0.3.0 */ @@ -42,7 +42,7 @@ final class FunctionalUnit extends Unit { /** * A function that accepts a value expressed in the unit and returns that * value expressed in the unit's base. - * + * * @since 2019-05-22 * @since v0.3.0 */ @@ -50,7 +50,7 @@ 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. @@ -72,7 +72,7 @@ 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. @@ -95,7 +95,7 @@ final class FunctionalUnit extends Unit { /** * {@inheritDoc} - * + * * Uses {@code converterFrom} to convert. */ @Override @@ -105,7 +105,7 @@ final class FunctionalUnit extends Unit { /** * {@inheritDoc} - * + * * Uses {@code converterTo} to convert. */ @Override diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 7191196..22105b6 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -26,7 +26,7 @@ import sevenUnits.utils.UncertainDouble; /** * A unit that can be expressed as a product of its base and a number. For * example, kilometres, inches and pounds. - * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.3.0 @@ -35,7 +35,7 @@ public final class LinearUnit extends Unit { /** * Gets a {@code LinearUnit} from a unit and a value. For example, converts * '59 °F' to a linear unit with the value of '288.15 K' - * + * * @param unit unit to convert * @param value value to convert * @return value expressed as a {@code LinearUnit} @@ -48,11 +48,11 @@ 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' - * + * * @param unit unit to convert * @param value value to convert * @param ns name(s) and symbol of unit @@ -67,7 +67,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), ns); } - + /** * @param unit unit to get base version of * @return the base unit associated with {@code unit}, as a @@ -78,12 +78,12 @@ public final class LinearUnit extends Unit { public static LinearUnit getBase(final Unit unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and * {@code conversionFactor}, expressed as a {@code LinearUnit}. - * + * * @param unitBase unit base to multiply by * @param conversionFactor number to multiply base by * @return product of base and conversion factor @@ -95,12 +95,12 @@ 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 * {@code conversionFactor}, expressed as a {@code LinearUnit}. - * + * * @param unitBase unit base to multiply by * @param conversionFactor number to multiply base by * @param ns name(s) and symbol of unit @@ -113,22 +113,22 @@ 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, - * + * *

     	 * this = conversionFactor * getBase()
     	 * 
    - * + * * @since 2019-10-16 * @since v0.3.0 */ private final double conversionFactor; - + /** * Creates the {@code LinearUnit}. - * + * * @param unitBase base of linear unit * @param conversionFactor conversion factor between base and unit * @since 2019-10-16 @@ -139,21 +139,21 @@ public final class LinearUnit extends Unit { super(unitBase, ns); this.conversionFactor = conversionFactor; } - + /** * {@inheritDoc} - * + * * Converts by dividing by {@code conversionFactor} */ @Override protected double convertFromBase(final double value) { return value / this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} value expressed in this unit to an * {@code UncertainValue} value expressed in {@code other}. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -170,35 +170,34 @@ public final class LinearUnit extends Unit { if (this.canConvertTo(other)) return value.timesExact( this.getConversionFactor() / other.getConversionFactor()); - else - throw new IllegalArgumentException( - String.format("Cannot convert from %s to %s.", this, other)); - + throw new IllegalArgumentException( + String.format("Cannot convert from %s to %s.", this, other)); + } - + /** * {@inheritDoc} - * + * * Converts by multiplying by {@code conversionFactor} */ @Override protected double convertToBase(final double value) { return value * this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} to the base unit. - * + * * @since 2020-09-07 * @since v0.3.0 */ UncertainDouble convertToBase(final UncertainDouble value) { return value.timesExact(this.getConversionFactor()); } - + /** * Divides this unit by a scalar. - * + * * @param divisor scalar to divide by * @return quotient * @since 2018-12-23 @@ -207,10 +206,10 @@ 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. - * + * * @param divisor unit to divide by * @return quotient of two units * @throws NullPointerException if {@code divisor} is null @@ -219,24 +218,24 @@ 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 base = this.getBase() + final var base = this.getBase() .dividedBy(divisor.getBase()); return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor()); } - + /** * {@inheritDoc} - * + * * Uses the base and conversion factor of units to test for equality. */ @Override public boolean equals(final Object obj) { if (!(obj instanceof LinearUnit)) return false; - final LinearUnit other = (LinearUnit) obj; + final var other = (LinearUnit) obj; return Objects.equals(this.getBase(), other.getBase()) && Double.compare(this.getConversionFactor(), other.getConversionFactor()) == 0; @@ -244,18 +243,18 @@ public final class LinearUnit extends Unit { /** * @param other unit to test equality with - * @return true iff this unit and other are equal, - * ignoring small differences caused by floating-point error. - * - * @apiNote This method is not transitive, - * so it cannot be used as an equals method. + * @return true iff this unit and other are equal, ignoring small differences + * caused by floating-point error. + * + * @apiNote This method is not transitive, so it cannot be used as an equals + * method. */ public boolean equalsApproximately(final LinearUnit other) { return Objects.equals(this.getBase(), other.getBase()) && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor()); } - + /** * @return conversion factor * @since 2019-10-16 @@ -264,10 +263,10 @@ public final class LinearUnit extends Unit { public double getConversionFactor() { return this.conversionFactor; } - + /** * {@inheritDoc} - * + * * Uses the base and conversion factor to compute a hash code. */ @Override @@ -275,7 +274,7 @@ public final class LinearUnit extends Unit { return 31 * this.getBase().hashCode() + Double.hashCode(this.getConversionFactor()); } - + /** * @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there * is a {@code BaseUnit b} where @@ -286,7 +285,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 @@ -295,7 +294,7 @@ public final class LinearUnit extends Unit { public boolean isCoherent() { return this.getConversionFactor() == 1; } - + /** * Returns the difference of this unit and another. *

    @@ -304,7 +303,7 @@ public final class LinearUnit extends Unit { * does not meet this condition, an {@code IllegalArgumentException} will be * thrown. *

    - * + * * @param subtrahend unit to subtract * @return difference of units * @throws IllegalArgumentException if {@code subtrahend} is not compatible @@ -315,18 +314,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. *

    @@ -335,7 +334,7 @@ public final class LinearUnit extends Unit { * does not meet this condition, an {@code IllegalArgumentException} will be * thrown. *

    - * + * * @param addend unit to add * @return sum of units * @throws IllegalArgumentException if {@code addend} is not compatible for @@ -346,21 +345,21 @@ 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. - * + * * @param multiplier scalar to multiply by * @return product * @since 2018-12-23 @@ -369,10 +368,10 @@ 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. - * + * * @param multiplier unit to multiply by * @return product of two units * @throws NullPointerException if {@code multiplier} is null @@ -381,24 +380,24 @@ 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 base = this.getBase() + final var 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. - * + * * @param exponent exponent to exponentiate unit to * @return exponentiated unit * @since 2019-01-15 @@ -408,11 +407,11 @@ public final class LinearUnit extends Unit { return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); } - + /** * Returns this unit to an exponent, rounding the resulting dimensions to the * nearest integer. - * + * * @param exponent exponent to raise unit to * @return result of rounded exponentation * @since 2024-08-22 @@ -423,12 +422,12 @@ public final class LinearUnit extends Unit { return valueOf(this.getBase().toExponentRounded(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. *

    @@ -438,7 +437,7 @@ public final class LinearUnit extends Unit { * have a symbol.
    * This method ignores alternate names of both this unit and the provided * prefix. - * + * * @param prefix prefix to apply * @return unit with prefix * @since 2019-03-18 @@ -446,8 +445,8 @@ public final class LinearUnit extends Unit { * @throws NullPointerException if prefix is null */ public LinearUnit withPrefix(final UnitPrefix prefix) { - final LinearUnit unit = this.times(prefix.getMultiplier()); - + final var unit = this.times(prefix.getMultiplier()); + // create new name and symbol, if possible final String name; if (this.getPrimaryName().isPresent() @@ -456,14 +455,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 86520d7..9a99e00 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -20,7 +20,6 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.ObjectProduct; @@ -28,10 +27,10 @@ import sevenUnits.utils.UncertainDouble; /** * A possibly uncertain value expressed in a linear unit. - * + * * Unless otherwise indicated, all methods in this class throw a * {@code NullPointerException} when an argument is null. - * + * * @author Adrien Hopkins * @since 2020-07-26 * @since v0.3.0 @@ -42,14 +41,14 @@ public final class LinearUnitValue { /** * Gets an exact {@code LinearUnitValue} - * + * * @param unit unit to express with * @param value value to express * @return exact {@code LinearUnitValue} instance * @since 2020-07-26 * @since v0.3.0 */ - public static final LinearUnitValue getExact(final LinearUnit unit, + public static LinearUnitValue getExact(final LinearUnit unit, final double value) { return new LinearUnitValue( Objects.requireNonNull(unit, "unit must not be null"), @@ -58,14 +57,14 @@ public final class LinearUnitValue { /** * Gets an uncertain {@code LinearUnitValue} - * + * * @param unit unit to express with * @param value value to express * @return uncertain {@code LinearUnitValue} instance * @since 2020-07-26 * @since v0.3.0 */ - public static final LinearUnitValue of(final LinearUnit unit, + public static LinearUnitValue of(final LinearUnit unit, final UncertainDouble value) { return new LinearUnitValue( Objects.requireNonNull(unit, "unit must not be null"), @@ -93,7 +92,7 @@ public final class LinearUnitValue { * @since 2020-08-04 * @since v0.3.0 */ - public final UnitValue asUnitValue() { + public UnitValue asUnitValue() { return UnitValue.of(this.unit, this.value.value()); } @@ -103,20 +102,20 @@ public final class LinearUnitValue { * @since 2020-07-26 * @since v0.3.0 */ - public final boolean canConvertTo(final LinearUnit other) { + public boolean canConvertTo(final LinearUnit other) { return this.unit.canConvertTo(other); } /** * Returns a LinearUnitValue that represents the same value expressed in a * different unit - * + * * @param other new unit to express value in * @return value expressed in {@code other} * @since 2020-07-26 * @since v0.3.0 */ - public final LinearUnitValue convertTo(final LinearUnit other) { + public LinearUnitValue convertTo(final LinearUnit other) { return LinearUnitValue.of(other, this.unit.convertTo(other, this.value)); } @@ -131,7 +130,7 @@ public final class LinearUnitValue { * @since 2024-08-15 * @since v1.0.0 */ - public final List convertToMultiple( + public List convertToMultiple( final List others) { if (others.size() < 1) throw new IllegalArgumentException("Must have at least one unit"); @@ -141,17 +140,17 @@ public final class LinearUnitValue { "All provided units must have the same base as the value."); } - LinearUnitValue remaining = this; + var remaining = this; final List values = new ArrayList<>(others.size()); for (final LinearUnit unit : others.subList(0, others.size() - 1)) { - final LinearUnitValue remainingInUnit = remaining.convertTo(unit); - final LinearUnitValue value = getExact(unit, + final var remainingInUnit = remaining.convertTo(unit); + final var value = getExact(unit, Math.floor(remainingInUnit.getValueExact() + 1e-12)); values.add(value); remaining = remaining.minus(value); } - final LinearUnitValue lastValue = remaining + final var lastValue = remaining .convertTo(others.get(others.size() - 1)); values.add(lastValue); return values; @@ -159,7 +158,7 @@ public final class LinearUnitValue { /** * Divides this value by a scalar - * + * * @param divisor value to divide by * @return multiplied value * @since 2020-07-28 @@ -171,7 +170,7 @@ public final class LinearUnitValue { /** * Divides this value by another value - * + * * @param divisor value to multiply by * @return quotient * @since 2020-07-28 @@ -186,7 +185,7 @@ public final class LinearUnitValue { * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 * km) returns true. - * + * * @since 2020-07-26 * @since v0.3.0 * @see #equals(Object, boolean) @@ -195,7 +194,7 @@ public final class LinearUnitValue { public boolean equals(final Object obj) { if (!(obj instanceof LinearUnitValue)) return false; - final LinearUnitValue other = (LinearUnitValue) obj; + final var other = (LinearUnitValue) obj; return Objects.equals(this.unit.getBase(), other.unit.getBase()) && this.unit.convertToBase(this.value) .equals(other.unit.convertToBase(other.value)); @@ -205,13 +204,13 @@ public final class LinearUnitValue { * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 * km) returns true. - * + * * @param obj object to test equality with * @param avoidFPErrors if true, this method will attempt to avoid * floating-point errors, at the cost of not always * being transitive. * @return true iff this and obj are equal - * + * * @since 2020-07-28 * @since v0.3.0 */ @@ -220,7 +219,7 @@ public final class LinearUnitValue { return this.equals(obj); if (!(obj instanceof LinearUnitValue)) return false; - final LinearUnitValue other = (LinearUnitValue) obj; + final var other = (LinearUnitValue) obj; return Objects.equals(this.unit.getBase(), other.unit.getBase()) && DecimalComparison.equals(this.unit.convertToBase(this.value), other.unit.convertToBase(other.value)); @@ -229,7 +228,7 @@ public final class LinearUnitValue { /** * @param other another {@code LinearUnitValue} * @return true iff this and other are within each other's uncertainty range - * + * * @since 2020-07-26 * @since v0.3.0 */ @@ -237,9 +236,9 @@ public final class LinearUnitValue { if (other == null || !Objects.equals(this.unit.getBase(), other.unit.getBase())) return false; - final LinearUnit base = LinearUnit.valueOf(this.unit.getBase(), 1); - final LinearUnitValue thisBase = this.convertTo(base); - final LinearUnitValue otherBase = other.convertTo(base); + final var base = LinearUnit.valueOf(this.unit.getBase(), 1); + final var thisBase = this.convertTo(base); + final var otherBase = other.convertTo(base); return thisBase.value.equivalent(otherBase.value); } @@ -249,7 +248,7 @@ public final class LinearUnitValue { * @since 2020-09-29 * @since v0.3.0 */ - public final LinearUnit getUnit() { + public LinearUnit getUnit() { return this.unit; } @@ -258,7 +257,7 @@ public final class LinearUnitValue { * @since 2020-09-29 * @since v0.3.0 */ - public final UncertainDouble getValue() { + public UncertainDouble getValue() { return this.value; } @@ -267,7 +266,7 @@ public final class LinearUnitValue { * @since 2020-09-07 * @since v0.3.0 */ - public final double getValueExact() { + public double getValueExact() { return this.value.value(); } @@ -280,7 +279,7 @@ public final class LinearUnitValue { /** * Returns the difference of this value and another, expressed in this * value's unit - * + * * @param subtrahend value to subtract * @return difference of values * @throws IllegalArgumentException if {@code subtrahend} has a unit that is @@ -296,14 +295,14 @@ public final class LinearUnitValue { "Incompatible units for subtraction \"%s\" and \"%s\".", this.unit, subtrahend.unit)); - final LinearUnitValue otherConverted = subtrahend.convertTo(this.unit); + final var otherConverted = subtrahend.convertTo(this.unit); return LinearUnitValue.of(this.unit, this.value.minus(otherConverted.value)); } /** * Returns the sum of this value and another, expressed in this value's unit - * + * * @param addend value to add * @return sum of values * @throws IllegalArgumentException if {@code addend} has a unit that is not @@ -319,14 +318,14 @@ public final class LinearUnitValue { "Incompatible units for addition \"%s\" and \"%s\".", this.unit, addend.unit)); - final LinearUnitValue otherConverted = addend.convertTo(this.unit); + final var otherConverted = addend.convertTo(this.unit); return LinearUnitValue.of(this.unit, this.value.plus(otherConverted.value)); } /** * Multiplies this value by a scalar - * + * * @param multiplier value to multiply by * @return multiplied value * @since 2020-07-28 @@ -338,7 +337,7 @@ public final class LinearUnitValue { /** * Multiplies this value by another value - * + * * @param multiplier value to multiply by * @return product * @since 2020-07-28 @@ -351,7 +350,7 @@ public final class LinearUnitValue { /** * Raises a value to an exponent - * + * * @param exponent exponent to raise to * @return result of exponentiation * @since 2020-07-28 @@ -364,7 +363,7 @@ public final class LinearUnitValue { /** * Raises this value to an exponent, rounding all dimensions to integers. - * + * * @param exponent exponent to raise this value to * @return result of exponentation * @@ -391,28 +390,28 @@ public final class LinearUnitValue { * single numbers. *

    * Non-exact values are rounded intelligently based on their uncertainty. - * + * * @param showUncertainty whether to show the value's uncertainty - * @param roundingMode how to round numbers in this string + * @param roundingMode how to round numbers in this string * @return string representing this value - * + * * @since 2020-07-26 * @since v0.3.0 */ public String toString(final boolean showUncertainty, RoundingMode roundingMode) { - final Optional primaryName = this.unit.getPrimaryName(); - final Optional symbol = this.unit.getSymbol(); - final String chosenName = symbol.orElse(primaryName.orElse(null)); + final var primaryName = this.unit.getPrimaryName(); + final var symbol = this.unit.getSymbol(); + final var chosenName = symbol.orElse(primaryName.orElse(null)); - final UncertainDouble baseValue = this.unit.convertToBase(this.value); + final var baseValue = this.unit.convertToBase(this.value); // get rounded strings // if showUncertainty is true, add brackets around the string - final String valueString = (showUncertainty ? "(" : "") + final var valueString = (showUncertainty ? "(" : "") + this.value.toString(showUncertainty, roundingMode) + (showUncertainty ? ")" : ""); - final String baseValueString = (showUncertainty ? "(" : "") + final var baseValueString = (showUncertainty ? "(" : "") + baseValue.toString(showUncertainty, roundingMode) + (showUncertainty ? ")" : ""); @@ -421,7 +420,6 @@ public final class LinearUnitValue { return String.format("%s unnamed unit (= %s %s)", valueString, baseValueString, this.unit.getBase() .toString(unit -> unit.getSymbol().orElseThrow())); - else - return String.format("%s %s", valueString, chosenName); + return String.format("%s %s", valueString, chosenName); } } diff --git a/src/main/java/sevenUnits/unit/LoadingException.java b/src/main/java/sevenUnits/unit/LoadingException.java index 7b3d708..2a75c99 100644 --- a/src/main/java/sevenUnits/unit/LoadingException.java +++ b/src/main/java/sevenUnits/unit/LoadingException.java @@ -22,15 +22,17 @@ import java.util.Optional; /** * An exception that occurred when loading a file. This wrapper class adds more * info about the error. - * + * * @author Adrien Hopkins * @since 2024-08-22 * @since v1.0.0 */ public final class LoadingException extends RuntimeException { /** The type of file that was being loaded. */ - public static enum FileType { - @SuppressWarnings("javadoc") UNIT, @SuppressWarnings("javadoc") DIMENSION + public enum FileType { + @SuppressWarnings("javadoc") + UNIT, @SuppressWarnings("javadoc") + DIMENSION } private static final long serialVersionUID = -8167971828216907607L; @@ -45,7 +47,7 @@ public final class LoadingException extends RuntimeException { /** * Create a LoadingException from some information, without a file. - * + * * @param lineNumber line number error happened on * @param line text of invalid line * @param fileType type of file @@ -63,7 +65,7 @@ public final class LoadingException extends RuntimeException { /** * Create a LoadingException from some information, with a file. - * + * * @param lineNumber line number error happened on * @param line text of invalid line * @param file file error happened on @@ -80,16 +82,12 @@ public final class LoadingException extends RuntimeException { this.problem = problem; } - /** - * @return the file this error happened in, if there is one - */ + /** @return the file this error happened in, if there is one */ public Optional file() { return this.file; } - /** - * @return type of file that this error happened in - */ + /** @return type of file that this error happened in */ public FileType fileType() { return this.fileType; } @@ -107,23 +105,17 @@ public final class LoadingException extends RuntimeException { this.line, this.problem)); } - /** - * @return text of line that caused this error - */ + /** @return text of line that caused this error */ public String line() { return this.line; } - /** - * @return number of line that caused this error - */ + /** @return number of line that caused this error */ public long lineNumber() { return this.lineNumber; } - /** - * @return the error, as an exception - */ + /** @return the error, as an exception */ public RuntimeException problem() { return this.problem; } diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 34fd0b8..e712dc3 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -24,16 +24,16 @@ import sevenUnits.utils.ObjectProduct; /** * All of the units, prefixes and dimensions that are used by the SI, as well as * some outside the SI. - * + * *

    * This class does not include prefixed units. To obtain prefixed units, use * {@link LinearUnit#withPrefix}: - * + * *

      * LinearUnit KILOMETRE = SI.METRE.withPrefix(SI.KILO);
      * 
    - * - * + * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.3.0 @@ -107,7 +107,7 @@ public final class Metric { /** * Constants that relate to the SI or other systems. - * + * * @author Adrien Hopkins * @since 2019-11-08 * @since v0.3.0 @@ -350,7 +350,7 @@ public final class Metric { pr -> 0.5 * Math.log(pr), Np -> Math.exp(2 * Np)) .withName(NameSymbol.of("neper", "Np")); public static final Unit BEL = Unit.fromConversionFunctions(ONE.getBase(), - pr -> Math.log10(pr), dB -> Math.pow(10, dB)) + Math::log10, dB -> Math.pow(10, dB)) .withName(NameSymbol.of("bel", "B")); public static final Unit DECIBEL = Unit .fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr), diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java index be8c5e2..ef12043 100644 --- a/src/main/java/sevenUnits/unit/USCustomary.java +++ b/src/main/java/sevenUnits/unit/USCustomary.java @@ -18,7 +18,7 @@ package sevenUnits.unit; /** * A static utility class that contains units in the US Customary system. - * + * * @author Adrien Hopkins * @since 2019-10-21 * @since v0.3.0 @@ -29,7 +29,7 @@ package sevenUnits.unit; public final class USCustomary { /** * US Customary units that measure area - * + * * @author Adrien Hopkins * @since 2019-11-08 * @since v0.3.0 @@ -48,7 +48,7 @@ public final class USCustomary { /** * US Customary units that measure length - * + * * @author Adrien Hopkins * @since 2019-10-28 * @since v0.3.0 @@ -79,7 +79,7 @@ public final class USCustomary { /** * mass units - * + * * @author Adrien Hopkins * @since 2019-11-08 * @since v0.3.0 @@ -100,7 +100,7 @@ public final class USCustomary { /** * Volume units - * + * * @author Adrien Hopkins * @since 2019-11-08 * @since v0.3.0 diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index d651fe2..40e6e0d 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -28,7 +28,7 @@ import sevenUnits.utils.ObjectProduct; /** * A unit that is composed of base units. - * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.3.0 @@ -37,14 +37,14 @@ public abstract class Unit implements Nameable { /** * Returns a unit from its base and the functions it uses to convert to and * from its base. - * + * *

    * For example, to get a unit representing the degree Celsius, the following * code can be used: - * + * * {@code Unit.fromConversionFunctions(SI.KELVIN, tempK -> tempK - 273.15, tempC -> tempC + 273.15);} *

    - * + * * @param base unit's base * @param converterFrom function that accepts a value expressed in the unit's * base and returns that value expressed in this unit. @@ -65,14 +65,14 @@ public abstract class Unit implements Nameable { /** * Returns a unit from its base and the functions it uses to convert to and * from its base. - * + * *

    * For example, to get a unit representing the degree Celsius, the following * code can be used: - * + * * {@code Unit.fromConversionFunctions(SI.KELVIN, tempK -> tempK - 273.15, tempC -> tempC + 273.15);} *

    - * + * * @param base unit's base * @param converterFrom function that accepts a value expressed in the unit's * base and returns that value expressed in this unit. @@ -93,7 +93,7 @@ public abstract class Unit implements Nameable { /** * The combination of units that this unit is based on. - * + * * @since 2019-10-16 * @since v0.3.0 */ @@ -101,7 +101,7 @@ public abstract class Unit implements Nameable { /** * This unit's name(s) and symbol - * + * * @since 2020-09-07 * @since v0.3.0 */ @@ -117,21 +117,20 @@ public abstract class Unit implements Nameable { /** * A constructor that constructs {@code BaseUnit} instances. - * + * * @since 2019-10-16 * @since v0.3.0 */ Unit(final NameSymbol nameSymbol) { - if (this instanceof BaseUnit) { - this.unitBase = ObjectProduct.oneOf((BaseUnit) this); - } else + if (!(this instanceof BaseUnit)) throw new AssertionError(); + this.unitBase = ObjectProduct.oneOf((BaseUnit) this); this.nameSymbol = nameSymbol; } /** * Creates the {@code Unit}. - * + * * @param unitBase base of unit * @param ns names and symbol of unit * @since 2019-10-16 @@ -147,7 +146,7 @@ public abstract class Unit implements Nameable { /** * Checks if a value expressed in this unit can be converted to a value * expressed in {@code other} - * + * * @param other unit or unitlike form to test with * @return true if they are compatible * @since 2019-01-13 @@ -171,10 +170,10 @@ public abstract class Unit implements Nameable { * If this unit is a base unit, this method should return * {@code value}. *

    - * + * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in base unit * @return value expressed in this unit * @since 2018-12-22 @@ -185,12 +184,12 @@ public abstract class Unit implements Nameable { /** * Converts a value expressed in this unit to a value expressed in * {@code other}. - * + * * @implSpec If unit conversion is possible, this implementation returns * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -205,9 +204,8 @@ public abstract class Unit implements Nameable { Objects.requireNonNull(other, "other must not be null."); if (this.canConvertTo(other)) return other.convertFromBase(this.convertToBase(value)); - else - throw new IllegalArgumentException( - String.format("Cannot convert from %s to %s.", this, other)); + throw new IllegalArgumentException( + String.format("Cannot convert from %s to %s.", this, other)); } /** @@ -222,10 +220,10 @@ public abstract class Unit implements Nameable { * If this unit is a base unit, this method should return * {@code value}. *

    - * + * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in this unit * @return value expressed in base unit * @since 2018-12-22 @@ -249,7 +247,7 @@ public abstract class Unit implements Nameable { */ public final ObjectProduct getDimension() { if (this.dimension == null) { - final Map mapping = this.unitBase.exponentMap(); + final var mapping = this.unitBase.exponentMap(); final Map dimensionMap = new HashMap<>(); for (final BaseUnit key : mapping.keySet()) { @@ -287,9 +285,9 @@ public abstract class Unit implements Nameable { *

    * All SI units (as designated by the BIPM) except the degree Celsius are * considered "metric" by this definition. - * + * * @return true iff this unit is metric. - * + * * @since 2020-08-27 * @since v0.3.0 */ @@ -297,7 +295,7 @@ public abstract class Unit implements Nameable { // first condition - check that it is a linear unit if (!(this instanceof LinearUnit)) return false; - final LinearUnit linear = (LinearUnit) this; + final var linear = (LinearUnit) this; // second condition - check that for (final BaseUnit b : linear.getBase().getBaseSet()) { @@ -318,9 +316,7 @@ public abstract class Unit implements Nameable { public String toDefinitionString() { if (!this.unitBase.getNameSymbol().isEmpty()) return "derived from " + this.unitBase.getName(); - else - return "derived from " - + this.getBase().toString(BaseUnit::getShortName); + return "derived from " + this.getBase().toString(BaseUnit::getShortName); } /** @@ -338,8 +334,7 @@ public abstract class Unit implements Nameable { && this.nameSymbol.getSymbol().isPresent()) return this.nameSymbol.getPrimaryName().orElseThrow() + " (" + this.nameSymbol.getSymbol().orElseThrow() + ")"; - else - return this.getName(); + return this.getName(); } /** diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index a85ec5f..05e9cc9 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -40,7 +40,6 @@ import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; -import java.util.regex.Matcher; import java.util.regex.Pattern; import sevenUnits.utils.ConditionalExistenceCollections; @@ -51,7 +50,7 @@ import sevenUnits.utils.UncertainDouble; /** * A database of units, prefixes and dimensions, and their names. - * + * * @author Adrien Hopkins * @since 2019-01-07 * @since v0.1.0 @@ -88,7 +87,7 @@ public final class UnitDatabase { * Because of ambiguities between prefixes (i.e. kilokilo = mega), * {@link #containsValue} and {@link #values()} currently ignore prefixes. *

    - * + * * @author Adrien Hopkins * @since 2019-04-13 * @since v0.2.0 @@ -96,7 +95,7 @@ public final class UnitDatabase { private static final class PrefixedUnitMap implements Map { /** * The class used for entry sets. - * + * *

    * If the map that created this set is infinite in size (has at least one * unit and at least one prefix), this set is infinite as well. If this @@ -104,7 +103,7 @@ public final class UnitDatabase { * {@code IllegalStateException} instead of creating an infinite-sized * array. *

    - * + * * @author Adrien Hopkins * @since 2019-04-13 * @since v0.2.0 @@ -113,7 +112,7 @@ public final class UnitDatabase { extends AbstractSet> { /** * The entry for this set. - * + * * @author Adrien Hopkins * @since 2019-04-14 * @since v0.2.0 @@ -125,7 +124,7 @@ public final class UnitDatabase { /** * Creates the {@code PrefixedUnitEntry}. - * + * * @param key key * @param value value * @since 2019-04-14 @@ -181,7 +180,7 @@ public final class UnitDatabase { * string is the string representation of the key, then the equals * ({@code =}) character, then the string representation of the * value. - * + * * @since 2019-05-03 * @since v0.3.0 */ @@ -194,7 +193,7 @@ public final class UnitDatabase { /** * An iterator that iterates over the units of a * {@code PrefixedUnitNameSet}. - * + * * @author Adrien Hopkins * @since 2019-04-14 * @since v0.2.0 @@ -214,9 +213,9 @@ public final class UnitDatabase { /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. - * + * * @param map map to base iterator on - * + * * @since 2019-04-14 * @since v0.2.0 */ @@ -232,7 +231,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private String getCurrentUnitName() { - final StringBuilder unitName = new StringBuilder(); + final var unitName = new StringBuilder(); for (final int i : this.prefixCoordinates) { unitName.append(this.prefixNames.get(i)); } @@ -245,18 +244,15 @@ public final class UnitDatabase { public boolean hasNext() { if (this.unitNames.isEmpty()) return false; - else { - if (this.prefixNames.isEmpty()) - return this.prefixCoordinates.isEmpty() - && this.unitNamePosition < this.unitNames.size(); - else - return true; - } + if (this.prefixNames.isEmpty()) + return this.prefixCoordinates.isEmpty() + && this.unitNamePosition < this.unitNames.size(); + return true; } /** * Changes this iterator's position to the next available one. - * + * * @since 2019-04-14 * @since v0.2.0 */ @@ -272,7 +268,7 @@ public final class UnitDatabase { this.prefixCoordinates.add(0, 0); } else { // get the prefix coordinate to increment, then increment - int i = this.prefixCoordinates.size() - 1; + var i = this.prefixCoordinates.size() - 1; this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1); @@ -298,7 +294,7 @@ public final class UnitDatabase { @Override public Entry next() { // get next element - final Entry nextEntry = this.peek(); + final var nextEntry = this.peek(); // iterate to next position this.incrementPosition(); @@ -327,7 +323,7 @@ public final class UnitDatabase { } } - final String nextName = this.getCurrentUnitName(); + final var nextName = this.getCurrentUnitName(); return new PrefixedUnitEntry(nextName, this.map.get(nextName)); } @@ -335,7 +331,7 @@ public final class UnitDatabase { /** * Returns a string representation of the object. The exact details * of the representation are unspecified and subject to change. - * + * * @since 2019-05-03 * @since v0.3.0 */ @@ -352,7 +348,7 @@ public final class UnitDatabase { /** * Creates the {@code PrefixedUnitNameSet}. - * + * * @param map map that created this set * @since 2019-04-13 * @since v0.2.0 @@ -389,7 +385,7 @@ public final class UnitDatabase { // This is OK because I'm in a try-catch block, catching the // exact exception that would be thrown. @SuppressWarnings("unchecked") - final Entry tempEntry = (Entry) o; + final var tempEntry = (Entry) o; entry = tempEntry; } catch (final ClassCastException e) { throw new IllegalArgumentException( @@ -447,55 +443,45 @@ public final class UnitDatabase { public int size() { if (this.map.units.isEmpty()) return 0; - else { - if (this.map.prefixes.isEmpty()) - return this.map.units.size(); - else - // infinite set - return Integer.MAX_VALUE; - } + if (this.map.prefixes.isEmpty()) + return this.map.units.size(); + // infinite set + return Integer.MAX_VALUE; } - /** - * @throws IllegalStateException if the set is infinite in size - */ + /** @throws IllegalStateException if the set is infinite in size */ @Override public Object[] toArray() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) return super.toArray(); - else - // infinite set - throw new IllegalStateException( - "Cannot make an infinite set into an array."); + // infinite set + throw new IllegalStateException( + "Cannot make an infinite set into an array."); } - /** - * @throws IllegalStateException if the set is infinite in size - */ + /** @throws IllegalStateException if the set is infinite in size */ @Override public T[] toArray(final T[] a) { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) return super.toArray(a); - else - // infinite set - throw new IllegalStateException( - "Cannot make an infinite set into an array."); + // infinite set + throw new IllegalStateException( + "Cannot make an infinite set into an array."); } @Override public String toString() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) return super.toString(); - else - return String.format( - "Infinite set of name-unit entries created from units %s and prefixes %s", - this.map.units, this.map.prefixes); + return String.format( + "Infinite set of name-unit entries created from units %s and prefixes %s", + this.map.units, this.map.prefixes); } } /** * The class used for unit name sets. - * + * *

    * If the map that created this set is infinite in size (has at least one * unit and at least one prefix), this set is infinite as well. If this @@ -503,7 +489,7 @@ public final class UnitDatabase { * {@code IllegalStateException} instead of creating an infinite-sized * array. *

    - * + * * @author Adrien Hopkins * @since 2019-04-13 * @since v0.2.0 @@ -513,7 +499,7 @@ public final class UnitDatabase { /** * An iterator that iterates over the units of a * {@code PrefixedUnitNameSet}. - * + * * @author Adrien Hopkins * @since 2019-04-14 * @since v0.2.0 @@ -533,9 +519,9 @@ public final class UnitDatabase { /** * Creates the * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}. - * + * * @param map map to base itorator on - * + * * @since 2019-04-14 * @since v0.2.0 */ @@ -551,7 +537,7 @@ public final class UnitDatabase { * @since v0.2.0 */ private String getCurrentUnitName() { - final StringBuilder unitName = new StringBuilder(); + final var unitName = new StringBuilder(); for (final int i : this.prefixCoordinates) { unitName.append(this.prefixNames.get(i)); } @@ -564,18 +550,15 @@ public final class UnitDatabase { public boolean hasNext() { if (this.unitNames.isEmpty()) return false; - else { - if (this.prefixNames.isEmpty()) - return this.prefixCoordinates.isEmpty() - && this.unitNamePosition < this.unitNames.size(); - else - return true; - } + if (this.prefixNames.isEmpty()) + return this.prefixCoordinates.isEmpty() + && this.unitNamePosition < this.unitNames.size(); + return true; } /** * Changes this iterator's position to the next available one. - * + * * @since 2019-04-14 * @since v0.2.0 */ @@ -591,7 +574,7 @@ public final class UnitDatabase { this.prefixCoordinates.add(0, 0); } else { // get the prefix coordinate to increment, then increment - int i = this.prefixCoordinates.size() - 1; + var i = this.prefixCoordinates.size() - 1; this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1); @@ -616,7 +599,7 @@ public final class UnitDatabase { @Override public String next() { - final String nextName = this.peek(); + final var nextName = this.peek(); this.incrementPosition(); @@ -649,7 +632,7 @@ public final class UnitDatabase { /** * Returns a string representation of the object. The exact details * of the representation are unspecified and subject to change. - * + * * @since 2019-05-03 * @since v0.3.0 */ @@ -666,7 +649,7 @@ public final class UnitDatabase { /** * Creates the {@code PrefixedUnitNameSet}. - * + * * @param map map that created this set * @since 2019-04-13 * @since v0.2.0 @@ -744,56 +727,46 @@ public final class UnitDatabase { public int size() { if (this.map.units.isEmpty()) return 0; - else { - if (this.map.prefixes.isEmpty()) - return this.map.units.size(); - else - // infinite set - return Integer.MAX_VALUE; - } + if (this.map.prefixes.isEmpty()) + return this.map.units.size(); + // infinite set + return Integer.MAX_VALUE; } - /** - * @throws IllegalStateException if the set is infinite in size - */ + /** @throws IllegalStateException if the set is infinite in size */ @Override public Object[] toArray() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) return super.toArray(); - else - // infinite set - throw new IllegalStateException( - "Cannot make an infinite set into an array."); + // infinite set + throw new IllegalStateException( + "Cannot make an infinite set into an array."); } - /** - * @throws IllegalStateException if the set is infinite in size - */ + /** @throws IllegalStateException if the set is infinite in size */ @Override public T[] toArray(final T[] a) { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) return super.toArray(a); - else - // infinite set - throw new IllegalStateException( - "Cannot make an infinite set into an array."); + // infinite set + throw new IllegalStateException( + "Cannot make an infinite set into an array."); } @Override public String toString() { if (this.map.units.isEmpty() || this.map.prefixes.isEmpty()) return super.toString(); - else - return String.format( - "Infinite set of name-unit entries created from units %s and prefixes %s", - this.map.units, this.map.prefixes); + return String.format( + "Infinite set of name-unit entries created from units %s and prefixes %s", + this.map.units, this.map.prefixes); } } /** * The units stored in this collection, without prefixes. - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -801,7 +774,7 @@ public final class UnitDatabase { /** * The available prefixes for use. - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -814,7 +787,7 @@ public final class UnitDatabase { /** * Creates the {@code PrefixedUnitMap}. - * + * * @param units map mapping unit names to units * @param prefixes map mapping prefix names to prefixes * @since 2019-04-13 @@ -865,11 +838,11 @@ public final class UnitDatabase { if (!(key instanceof String)) throw new IllegalArgumentException( "Attempted to test for a unit using a non-string name."); - final String unitName = (String) key; + final var unitName = (String) key; // Then, look for the longest prefix that is attached to a valid unit String longestPrefix = null; - int longestLength = 0; + var longestLength = 0; for (final String prefixName : this.prefixes.keySet()) { // a prefix name is valid if: @@ -881,7 +854,7 @@ public final class UnitDatabase { // linear units can have prefixes) if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) { - final String rest = unitName.substring(prefixName.length()); + final var rest = unitName.substring(prefixName.length()); if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) { longestPrefix = prefixName; @@ -895,7 +868,7 @@ public final class UnitDatabase { /** * {@inheritDoc} - * + * *

    * Because of ambiguities between prefixes (i.e. kilokilo = mega), this * method only tests for prefixless units. @@ -924,11 +897,11 @@ public final class UnitDatabase { if (!(key instanceof String)) throw new IllegalArgumentException( "Attempted to obtain a unit using a non-string name."); - final String unitName = (String) key; + final var unitName = (String) key; // Then, look for the longest prefix that is attached to a valid unit String longestPrefix = null; - int longestLength = 0; + var longestLength = 0; for (final String prefixName : this.prefixes.keySet()) { // a prefix name is valid if: @@ -940,7 +913,7 @@ public final class UnitDatabase { // linear units can have prefixes) if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) { - final String rest = unitName.substring(prefixName.length()); + final var rest = unitName.substring(prefixName.length()); if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) { longestPrefix = prefixName; @@ -952,16 +925,14 @@ public final class UnitDatabase { // if none found, returns null if (longestPrefix == null) return null; - else { - // get necessary data - final String rest = unitName.substring(longestLength); - // this cast will not fail because I verified that it would work - // before selecting this prefix - final LinearUnit unit = (LinearUnit) this.get(rest); - final UnitPrefix prefix = this.prefixes.get(longestPrefix); - - return unit.withPrefix(prefix); - } + // get necessary data + final var rest = unitName.substring(longestLength); + // this cast will not fail because I verified that it would work + // before selecting this prefix + final var unit = (LinearUnit) this.get(rest); + final var prefix = this.prefixes.get(longestPrefix); + + return unit.withPrefix(prefix); } @Override @@ -1038,28 +1009,24 @@ public final class UnitDatabase { public int size() { if (this.units.isEmpty()) return 0; - else { - if (this.prefixes.isEmpty()) - return this.units.size(); - else - // infinite set - return Integer.MAX_VALUE; - } + if (this.prefixes.isEmpty()) + return this.units.size(); + // infinite set + return Integer.MAX_VALUE; } @Override public String toString() { if (this.units.isEmpty() || this.prefixes.isEmpty()) return new HashMap<>(this).toString(); - else - return String.format( - "Infinite map of name-unit entries created from units %s and prefixes %s", - this.units, this.prefixes); + return String.format( + "Infinite map of name-unit entries created from units %s and prefixes %s", + this.units, this.prefixes); } /** * {@inheritDoc} - * + * *

    * Because of ambiguities between prefixes (i.e. kilokilo = mega), this * method ignores prefixes. @@ -1090,60 +1057,60 @@ public final class UnitDatabase { /** * The exponent operator - * + * * @param base base of exponentiation * @param exponentUnit exponent * @return result * @since 2019-04-10 * @since v0.2.0 */ - private static final LinearUnit exponentiateUnits(final LinearUnit base, + private static LinearUnit exponentiateUnits(final LinearUnit base, final LinearUnit exponentUnit) { if (!exponentUnit.getBase().equals(Metric.ONE.getBase())) throw new IllegalArgumentException(String.format( "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", base, exponentUnit)); - final double exponent = exponentUnit.getConversionFactor(); + final var exponent = exponentUnit.getConversionFactor(); return base.toExponentRounded(exponent); } /** * The exponent operator - * + * * @param base base of exponentiation * @param exponentUnit exponent * @return result * @since 2020-08-04 * @since v0.3.0 */ - private static final LinearUnitValue exponentiateUnitValues( + private static LinearUnitValue exponentiateUnitValues( final LinearUnitValue base, final LinearUnitValue exponentValue) { if (!exponentValue.canConvertTo(Metric.ONE)) throw new IllegalArgumentException(String.format( "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", base, exponentValue)); - final double exponent = exponentValue.getValueExact(); + final var exponent = exponentValue.getValueExact(); return base.toExponentRounded(exponent); } /** * Formats an expression so it can be parsed by the expression parser. - * + * * Specifically, puts spaces around all operators so they can be parsed as * words. - * + * * @param expression expression to format * @return formatted expression * @since 2025-06-07 * @since v1.0.0 */ - static final String formatExpression(String expression) { - String modifiedExpression = expression; + static String formatExpression(String expression) { + var modifiedExpression = expression; for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) { - modifiedExpression = modifiedExpression.replaceAll( - operator, " " + operator + " "); + modifiedExpression = modifiedExpression.replaceAll(operator, + " " + operator + " "); } modifiedExpression = modifiedExpression.replaceAll("\\s+", " "); @@ -1158,8 +1125,8 @@ public final class UnitDatabase { static boolean isRemovableDuplicate(Map map, Entry entry) { for (final Entry e : map.entrySet()) { - final String name = e.getKey(); - final T value = e.getValue(); + final var name = e.getKey(); + final var value = e.getValue(); if (lengthFirstComparator.compare(entry.getKey(), name) < 0 && Objects.equals(map.get(entry.getKey()), value)) return true; @@ -1169,7 +1136,7 @@ public final class UnitDatabase { /** * The units in this system, excluding prefixes. - * + * * @since 2019-01-07 * @since v0.1.0 */ @@ -1177,7 +1144,7 @@ public final class UnitDatabase { /** * The unit prefixes in this system. - * + * * @since 2019-01-14 * @since v0.1.0 */ @@ -1185,7 +1152,7 @@ public final class UnitDatabase { /** * The dimensions in this system. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -1193,7 +1160,7 @@ public final class UnitDatabase { /** * A map mapping strings to units (including prefixes) - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -1201,7 +1168,7 @@ public final class UnitDatabase { /** * A map mapping strings to unit sets - * + * * @since 2024-08-16 * @since v1.0.0 */ @@ -1221,74 +1188,74 @@ public final class UnitDatabase { /** * A parser that can parse unit expressions. - * + * * @since 2019-03-22 * @since v0.2.0 */ private final ExpressionParser unitExpressionParser = new ExpressionParser.Builder<>( - this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) - .addBinaryOperator("space_times", (o1, o2) -> o1.times(o2), 2) + this::getLinearUnit).addBinaryOperator("+", LinearUnit::plus, 0) + .addBinaryOperator("-", LinearUnit::minus, 0) + .addBinaryOperator("*", LinearUnit::times, 1) + .addBinaryOperator("space_times", LinearUnit::times, 2) .addSpaceFunction("space_times") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 4) + .addBinaryOperator("/", LinearUnit::dividedBy, 1) + .addBinaryOperator("|", LinearUnit::dividedBy, 4) .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 3).build(); /** * A parser that can parse unit value expressions. - * + * * @since 2020-08-04 * @since v0.3.0 */ private final ExpressionParser 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) - .addBinaryOperator("space_times", (o1, o2) -> o1.times(o2), 2) + .addBinaryOperator("+", LinearUnitValue::plus, 0) + .addBinaryOperator("-", LinearUnitValue::minus, 0) + .addBinaryOperator("*", LinearUnitValue::times, 1) + .addBinaryOperator("space_times", LinearUnitValue::times, 2) .addSpaceFunction("space_times") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 4) + .addBinaryOperator("/", LinearUnitValue::dividedBy, 1) + .addBinaryOperator("|", LinearUnitValue::dividedBy, 4) .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 3) .build(); /** * A parser that can parse unit prefix expressions - * + * * @since 2019-04-13 * @since v0.2.0 */ private final ExpressionParser prefixExpressionParser = new ExpressionParser.Builder<>( - this::getPrefix).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0) - .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0) - .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1) + this::getPrefix).addBinaryOperator("+", UnitPrefix::plus, 0) + .addBinaryOperator("-", UnitPrefix::minus, 0) + .addBinaryOperator("*", UnitPrefix::times, 1) .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1) - .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 3) + .addBinaryOperator("/", UnitPrefix::dividedBy, 1) + .addBinaryOperator("|", UnitPrefix::dividedBy, 3) .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), 2) .build(); /** * A parser that can parse unit dimension expressions. - * + * * @since 2019-04-13 * @since v0.2.0 */ private final ExpressionParser> unitDimensionParser = new ExpressionParser.Builder<>( - this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0) + this::getDimension).addBinaryOperator("*", ObjectProduct::times, 0) .addSpaceFunction("*") - .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0) - .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2) + .addBinaryOperator("/", ObjectProduct::dividedBy, 0) + .addBinaryOperator("|", ObjectProduct::dividedBy, 2) .addNumericOperator("^", (o1, o2) -> { - final int exponent = (int) Math.round(o2.value()); + final var exponent = (int) Math.round(o2.value()); return o1.toExponent(exponent); }, 1).build(); /** * Creates the {@code UnitsDatabase}. - * + * * @since 2019-01-10 * @since v0.1.0 */ @@ -1298,7 +1265,7 @@ public final class UnitDatabase { /** * Creates the {@code UnitsDatabase} - * + * * @param prefixRepetitionRule the rule that determines when prefix * repetition is allowed * @since 2020-08-26 @@ -1318,7 +1285,7 @@ public final class UnitDatabase { /** * Adds a unit dimension to the database. - * + * * @param name dimension's name * @param dimension dimension to add * @throws NullPointerException if name or dimension is null @@ -1329,14 +1296,14 @@ public final class UnitDatabase { final ObjectProduct dimension) { Objects.requireNonNull(name, "name may not be null"); Objects.requireNonNull(dimension, "dimension may not be null"); - final ObjectProduct namedDimension = dimension + final var namedDimension = dimension .withName(dimension.getNameSymbol().withExtraName(name)); this.dimensions.put(name, namedDimension); } /** * Adds to the list from a line in a unit dimension file. - * + * * @param line line to look at * @param lineCounter number of line, for error messages * @since 2019-04-10 @@ -1354,13 +1321,13 @@ public final class UnitDatabase { } // divide line into name and expression - final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); + final var lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) throw new IllegalArgumentException(String.format( "Error at line %d: Lines of a dimension file must consist of a dimension name, then spaces or tabs, then a dimension expression.", lineCounter)); - final String name = lineMatcher.group(1); - final String expression = lineMatcher.group(2); + final var name = lineMatcher.group(1); + final var expression = lineMatcher.group(2); // if (name.endsWith(" ")) { // System.err.printf("Warning - line %d's dimension name ends in a space", @@ -1380,7 +1347,7 @@ public final class UnitDatabase { /** * Adds a unit prefix to the database. - * + * * @param name prefix's name * @param prefix prefix to add * @throws NullPointerException if name or prefix is null @@ -1397,7 +1364,7 @@ public final class UnitDatabase { /** * Adds a unit to the database. - * + * * @param name unit's name * @param unit unit to add * @throws NullPointerException if unit is null @@ -1414,7 +1381,7 @@ public final class UnitDatabase { /** * Adds to the list from a line in a unit file. - * + * * @param line line to look at * @param lineCounter number of line, for error messages * @since 2019-04-10 @@ -1432,14 +1399,14 @@ public final class UnitDatabase { } // divide line into name and expression - final Matcher lineMatcher = NAME_EXPRESSION.matcher(line); + final var lineMatcher = NAME_EXPRESSION.matcher(line); if (!lineMatcher.matches()) throw new IllegalArgumentException(String.format( "Error at line %d: Lines of a unit file must consist of a unit name, then spaces or tabs, then a unit expression.", lineCounter)); - final String name = lineMatcher.group(1); + final var name = lineMatcher.group(1); - final String expression = lineMatcher.group(2); + final var expression = lineMatcher.group(2); // this code should never occur // if (name.endsWith(" ")) { @@ -1453,18 +1420,15 @@ public final class UnitDatabase { if (!this.containsUnitName(name)) throw new IllegalArgumentException(String .format("! used but no unit found (line %d).", lineCounter)); + } else if (name.endsWith("-")) { + final var prefixName = name.substring(0, name.length() - 1); + this.addPrefix(prefixName, this.getPrefixFromExpression(expression)); + } else if (expression.contains(";")) { + // it's a multi-unit + this.addUnitSet(name, this.getUnitSetFromExpression(expression)); } else { - if (name.endsWith("-")) { - final String prefixName = name.substring(0, name.length() - 1); - this.addPrefix(prefixName, - this.getPrefixFromExpression(expression)); - } else if (expression.contains(";")) { - // it's a multi-unit - this.addUnitSet(name, this.getUnitSetFromExpression(expression)); - } else { - // it's a unit, get the unit - this.addUnit(name, this.getUnitFromExpression(expression)); - } + // it's a unit, get the unit + this.addUnit(name, this.getUnitFromExpression(expression)); } } @@ -1491,7 +1455,7 @@ public final class UnitDatabase { /** * Removes all units, unit sets, prefixes and dimensions from this database. - * + * * @since 2022-02-26 * @since v0.4.0 */ @@ -1504,7 +1468,7 @@ public final class UnitDatabase { /** * Tests if the database has a unit dimension with this name. - * + * * @param name name to test * @return if database contains name * @since 2019-03-14 @@ -1516,7 +1480,7 @@ public final class UnitDatabase { /** * Tests if the database has a unit prefix with this name. - * + * * @param name name to test * @return if database contains name * @since 2019-01-13 @@ -1529,7 +1493,7 @@ public final class UnitDatabase { /** * Tests if the database has a unit with this name, taking prefixes into * consideration - * + * * @param name name to test * @return if database contains name * @since 2019-01-13 @@ -1541,7 +1505,7 @@ public final class UnitDatabase { /** * Returns true iff there is a unit set with this name. - * + * * @param name name to check for * @return true iff there is a unit set with this name * @@ -1564,7 +1528,7 @@ public final class UnitDatabase { /** * Evaluates a unit expression, following the same rules as * {@link #getUnitFromExpression}. - * + * * @param expression expression to parse * @return {@code LinearUnitValue} representing value of expression * @since 2020-08-04 @@ -1583,7 +1547,7 @@ public final class UnitDatabase { /** * Gets a unit dimension from the database using its name. - * + * * @param name dimension's name * @return dimension * @since 2019-03-14 @@ -1591,12 +1555,11 @@ public final class UnitDatabase { */ public ObjectProduct getDimension(final String name) { Objects.requireNonNull(name, "name must not be null."); - final ObjectProduct dimension = this.dimensions.get(name); + final var dimension = this.dimensions.get(name); if (dimension == null) throw new NoSuchElementException( "No dimension with name \"" + name + "\"."); - else - return dimension; + return dimension; } /** @@ -1611,7 +1574,7 @@ public final class UnitDatabase { * multiplication)

  • *
  • The operator '^' which exponentiates. Exponents must be integers.
  • * - * + * * @param expression expression to parse * @return parsed unit dimension * @throws IllegalArgumentException if the expression cannot be parsed @@ -1627,13 +1590,14 @@ public final class UnitDatabase { if (this.containsDimensionName(expression)) return this.getDimension(expression); - return this.unitDimensionParser.parseExpression(formatExpression(expression)); + return this.unitDimensionParser + .parseExpression(formatExpression(expression)); } /** * Gets a unit. If it is linear, cast it to a LinearUnit and return it. * Otherwise, throw an {@code IllegalArgumentException}. - * + * * @param name unit's name * @return unit * @since 2019-03-22 @@ -1650,26 +1614,25 @@ public final class UnitDatabase { "Format nonlinear units like: unit(value)."); // solve the function - final Unit unit = this.getUnit(parts.get(0)); - final double value = Double.parseDouble( + final var unit = this.getUnit(parts.get(0)); + final var value = Double.parseDouble( parts.get(1).substring(0, parts.get(1).length() - 1)); return LinearUnit.fromUnitValue(unit, value); - } else { - // get a linear unit - final Unit unit = this.getUnit(name); - - if (unit instanceof LinearUnit) - return (LinearUnit) unit; - else - throw new IllegalArgumentException( - String.format("%s is not a linear unit.", name)); } + // get a linear unit + final var unit = this.getUnit(name); + + if (unit instanceof LinearUnit) + return (LinearUnit) unit; + else + throw new IllegalArgumentException( + String.format("%s is not a linear unit.", name)); } /** * Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be * converted to their base units. - * + * * @param name name of unit * @return {@code LinearUnitValue} instance * @since 2020-08-04 @@ -1687,7 +1650,7 @@ public final class UnitDatabase { /** * Gets a unit prefix from the database from its name - * + * * @param name prefix's name * @return prefix * @since 2019-01-10 @@ -1697,18 +1660,17 @@ public final class UnitDatabase { try { return UnitPrefix.valueOf(Double.parseDouble(name)); } catch (final NumberFormatException e) { - final UnitPrefix prefix = this.prefixes.get(name); + final var prefix = this.prefixes.get(name); if (prefix == null) throw new NoSuchElementException( "No prefix with name \"" + name + "\"."); - else - return prefix; + return prefix; } } /** * Gets all of the prefixes that are on a unit name, in application order. - * + * * @param unitName name of unit * @return prefixes * @since 2020-08-26 @@ -1716,12 +1678,12 @@ public final class UnitDatabase { */ List getPrefixesFromName(final String unitName) { final List prefixes = new ArrayList<>(); - String name = unitName; + var name = unitName; while (!this.prefixlessUnits.containsKey(name)) { // find the longest prefix String longestPrefixName = null; - int longestLength = name.length(); + var longestLength = name.length(); while (longestPrefixName == null) { longestLength--; @@ -1734,7 +1696,7 @@ public final class UnitDatabase { } // longest prefix found! - final UnitPrefix prefix = this.getPrefix(longestPrefixName); + final var prefix = this.getPrefix(longestPrefixName); prefixes.add(0, prefix); name = name.substring(longestLength); } @@ -1747,7 +1709,7 @@ public final class UnitDatabase { * Currently, prefix expressions are much simpler than unit expressions: They * are either a number or the name of another prefix *

    - * + * * @param expression expression to input * @return prefix * @throws IllegalArgumentException if expression cannot be parsed @@ -1762,7 +1724,8 @@ public final class UnitDatabase { if (this.containsUnitName(expression)) return this.getPrefix(expression); - return this.prefixExpressionParser.parseExpression(formatExpression(expression)); + return this.prefixExpressionParser + .parseExpression(formatExpression(expression)); } /** @@ -1770,13 +1733,13 @@ public final class UnitDatabase { * @since 2020-08-26 * @since v0.3.0 */ - public final Predicate> getPrefixRepetitionRule() { + public Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } /** * Gets a unit from the database from its name, looking for prefixes. - * + * * @param name unit's name * @return unit * @since 2019-01-10 @@ -1784,27 +1747,28 @@ public final class UnitDatabase { */ public Unit getUnit(final String name) { try { - final double value = Double.parseDouble(name); + final var value = Double.parseDouble(name); return Metric.ONE.times(value); } catch (final NumberFormatException e) { - final Unit unit = this.units.get(name); + final var unit = this.units.get(name); if (unit == null) throw new NoSuchElementException("No unit " + name); - else if (unit.getPrimaryName().isEmpty()) + if (unit.getPrimaryName().isEmpty()) return unit.withName(NameSymbol.ofName(name)); - else if (!unit.getPrimaryName().get().equals(name)) { + if (!unit.getPrimaryName().get().equals(name)) { final Set otherNames = new HashSet<>(unit.getOtherNames()); otherNames.add(unit.getPrimaryName().get()); return unit.withName(NameSymbol.ofNullable(name, unit.getSymbol().orElse(null), otherNames)); - } else if (!unit.getOtherNames().contains(name)) { + } + if (!unit.getOtherNames().contains(name)) { final Set otherNames = new HashSet<>(unit.getOtherNames()); otherNames.add(name); return unit.withName( NameSymbol.ofNullable(unit.getPrimaryName().orElse(null), unit.getSymbol().orElse(null), otherNames)); - } else - return unit; + } + return unit; } } @@ -1823,7 +1787,7 @@ public final class UnitDatabase { *
  • A number which is multiplied or divided
  • * * This method only works with linear units. - * + * * @param expression expression to parse * @return parsed unit * @throws IllegalArgumentException if the expression cannot be parsed @@ -1845,7 +1809,7 @@ public final class UnitDatabase { /** * Get a unit set from its name, throwing a {@link NoSuchElementException} if * there is none. - * + * * @param name name of unit set * @return unit set with that name * @@ -1853,7 +1817,7 @@ public final class UnitDatabase { * @since v1.0.0 */ public List getUnitSet(String name) { - final List unitSet = this.unitSets.get(name); + final var unitSet = this.unitSets.get(name); if (unitSet == null) throw new NoSuchElementException("No unit set with name " + name); return unitSet; @@ -1866,20 +1830,19 @@ public final class UnitDatabase { * @since v1.0.0 */ List getUnitSetFromExpression(String expression) { - final String[] parts = expression.split(";"); + final var parts = expression.split(";"); final List units = new ArrayList<>(parts.length); for (final String unitName : parts) { - final Unit unit = this.getUnitFromExpression(unitName.trim()); + final var unit = this.getUnitFromExpression(unitName.trim()); - if (!(unit instanceof LinearUnit)) { + if (!(unit instanceof LinearUnit)) throw new IllegalArgumentException(String.format( "Unit '%s' is in a unit-set expression, but is not linear.", unitName)); - } else if (units.size() > 0 && !unit.canConvertTo(units.get(0))) { + if (units.size() > 0 && !unit.canConvertTo(units.get(0))) throw new IllegalArgumentException(String.format( "Units in expression '%s' have different dimensions.", expression)); - } units.add((LinearUnit) unit); } @@ -1904,7 +1867,7 @@ public final class UnitDatabase { * no unit is found, an IllegalArgumentException is thrown. This is used to * define initial units and ensure that the database contains them. * - * + * * @param file file to read * @throws NullPointerException if file is null * @return list of errors that happened when loading file @@ -1915,7 +1878,7 @@ public final class UnitDatabase { Objects.requireNonNull(file, "file must not be null."); final List errors = new ArrayList<>(); try { - long lineCounter = 0; + var lineCounter = 0L; for (final String line : Files.readAllLines(file)) { try { this.addDimensionFromLine(line, ++lineCounter); @@ -1944,10 +1907,10 @@ public final class UnitDatabase { public List loadDimensionsFromStream( final InputStream stream) { final List errors = new ArrayList<>(); - try (final Scanner scanner = new Scanner(stream)) { - long lineCounter = 0; + try (final var scanner = new Scanner(stream)) { + var lineCounter = 0L; while (scanner.hasNextLine()) { - final String line = scanner.nextLine(); + final var line = scanner.nextLine(); try { this.addDimensionFromLine(line, ++lineCounter); } catch (IllegalArgumentException | NoSuchElementException e) { @@ -1976,7 +1939,7 @@ public final class UnitDatabase { * no unit is found, an IllegalArgumentException is thrown. This is used to * define initial units and ensure that the database contains them. * - * + * * @param file file to read * @throws NullPointerException if file is null * @return list of errors that happened when loading file @@ -1987,7 +1950,7 @@ public final class UnitDatabase { Objects.requireNonNull(file, "file must not be null."); final List errors = new ArrayList<>(); try { - long lineCounter = 0; + var lineCounter = 0L; for (final String line : Files.readAllLines(file)) { try { this.addUnitOrPrefixFromLine(line, ++lineCounter); @@ -2015,10 +1978,10 @@ public final class UnitDatabase { */ public List loadUnitsFromStream(InputStream stream) { final List errors = new ArrayList<>(); - try (final Scanner scanner = new Scanner(stream)) { - long lineCounter = 0; + try (final var scanner = new Scanner(stream)) { + var lineCounter = 0L; while (scanner.hasNextLine()) { - final String line = scanner.nextLine(); + final var line = scanner.nextLine(); try { this.addUnitOrPrefixFromLine(line, ++lineCounter); } catch (IllegalArgumentException | NoSuchElementException e) { @@ -2039,10 +2002,9 @@ public final class UnitDatabase { public Map prefixMap(boolean includeDuplicates) { if (includeDuplicates) return Collections.unmodifiableMap(this.prefixes); - else - return Collections.unmodifiableMap(ConditionalExistenceCollections - .conditionalExistenceMap(this.prefixes, - entry -> !isRemovableDuplicate(this.prefixes, entry))); + return Collections.unmodifiableMap(ConditionalExistenceCollections + .conditionalExistenceMap(this.prefixes, + entry -> !isRemovableDuplicate(this.prefixes, entry))); } /** @@ -2050,7 +2012,7 @@ public final class UnitDatabase { * @since 2020-08-26 * @since v0.3.0 */ - public final void setPrefixRepetitionRule( + public void setPrefixRepetitionRule( Predicate> prefixRepetitionRule) { this.prefixRepetitionRule = prefixRepetitionRule; } @@ -2089,7 +2051,7 @@ public final class UnitDatabase { * {@link PrefixedUnitMap#values() values()} methods currently ignore * prefixes. *

    - * + * * @return a map mapping unit names to units, including prefixed names * @since 2019-04-13 * @since v0.2.0 @@ -2110,11 +2072,9 @@ public final class UnitDatabase { public Map unitMapPrefixless(boolean includeDuplicates) { if (includeDuplicates) return Collections.unmodifiableMap(this.prefixlessUnits); - else - return Collections.unmodifiableMap(ConditionalExistenceCollections - .conditionalExistenceMap(this.prefixlessUnits, - entry -> !isRemovableDuplicate(this.prefixlessUnits, - entry))); + return Collections.unmodifiableMap(ConditionalExistenceCollections + .conditionalExistenceMap(this.prefixlessUnits, + entry -> !isRemovableDuplicate(this.prefixlessUnits, entry))); } /** diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index 0fd3421..af106b9 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -25,7 +25,7 @@ import sevenUnits.utils.Nameable; /** * A prefix that can be applied to a {@code LinearUnit} to multiply it by some * value - * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.3.0 @@ -33,7 +33,7 @@ import sevenUnits.utils.Nameable; public final class UnitPrefix implements Nameable { /** * Gets a {@code UnitPrefix} from a multiplier - * + * * @param multiplier multiplier of prefix * @return prefix * @since 2019-10-16 @@ -45,7 +45,7 @@ public final class UnitPrefix implements Nameable { /** * Gets a {@code UnitPrefix} from a multiplier and a name - * + * * @param multiplier multiplier of prefix * @param ns name(s) and symbol of prefix * @return prefix @@ -61,7 +61,7 @@ public final class UnitPrefix implements Nameable { /** * This prefix's name(s) and symbol. - * + * * @since 2022-04-16 * @since v0.4.0 */ @@ -69,7 +69,7 @@ public final class UnitPrefix implements Nameable { /** * The number that this prefix multiplies units by - * + * * @since 2019-10-16 * @since v0.3.0 */ @@ -77,7 +77,7 @@ public final class UnitPrefix implements Nameable { /** * Creates the {@code DefaultUnitPrefix}. - * + * * @param multiplier * @since 2019-01-14 * @since v0.2.0 @@ -89,7 +89,7 @@ public final class UnitPrefix implements Nameable { /** * Divides this prefix by a scalar - * + * * @param divisor number to divide by * @return quotient of prefix and scalar * @since 2019-10-16 @@ -101,7 +101,7 @@ public final class UnitPrefix implements Nameable { /** * Divides this prefix by {@code other}. - * + * * @param other prefix to divide by * @return quotient of prefixes * @since 2019-04-13 @@ -113,29 +113,26 @@ public final class UnitPrefix implements Nameable { /** * {@inheritDoc} - * + * * Uses the prefix's multiplier to determine equality. */ @Override public boolean equals(final Object obj) { if (this == obj) return true; - if (obj == null) - return false; - if (!(obj instanceof UnitPrefix)) + if ((obj == null) || !(obj instanceof UnitPrefix)) return false; - final UnitPrefix other = (UnitPrefix) obj; - return Double.compare(this.getMultiplier(), - other.getMultiplier()) == 0; + final var other = (UnitPrefix) obj; + return Double.compare(this.getMultiplier(), other.getMultiplier()) == 0; } /** * @param other prefix to compare to - * @return true iff this prefix and other are equal, - * ignoring small differences caused by floating-point error. - * - * @apiNote This method is not transitive, - * so it cannot be used as an equals method. + * @return true iff this prefix and other are equal, ignoring small + * differences caused by floating-point error. + * + * @apiNote This method is not transitive, so it cannot be used as an equals + * method. */ public boolean equalsApproximately(final UnitPrefix other) { if (this == other) @@ -162,7 +159,7 @@ public final class UnitPrefix implements Nameable { /** * {@inheritDoc} - * + * * Uses the prefix's multiplier to determine a hash code. */ @Override @@ -171,22 +168,24 @@ public final class UnitPrefix implements Nameable { } /** - * Multiplies this prefix by a scalar - * - * @param multiplicand number to multiply by - * @return product of prefix and scalar - * @since 2019-10-16 - * @since v0.3.0 + * Subtracts {@code other} from this prefix and returns the result. + * + * @param other prefix to subtract + * @return difference of prefixes + * + * @since 2024-03-03 + * @since v0.5.0 */ - public UnitPrefix times(final double multiplicand) { - return valueOf(this.getMultiplier() * multiplicand); + public UnitPrefix minus(final UnitPrefix other) { + return valueOf(this.getMultiplier() - other.getMultiplier()); } /** * Adds {@code other} to this prefix and returns the result. + * * @param other prefix to add * @return sum of prefixes - * + * * @since 2024-03-03 * @since v0.5.0 */ @@ -195,20 +194,20 @@ public final class UnitPrefix implements Nameable { } /** - * Subtracts {@code other} from this prefix and returns the result. - * @param other prefix to subtract - * @return difference of prefixes - * - * @since 2024-03-03 - * @since v0.5.0 + * Multiplies this prefix by a scalar + * + * @param multiplicand number to multiply by + * @return product of prefix and scalar + * @since 2019-10-16 + * @since v0.3.0 */ - public UnitPrefix minus(final UnitPrefix other) { - return valueOf(this.getMultiplier() - other.getMultiplier()); + public UnitPrefix times(final double multiplicand) { + return valueOf(this.getMultiplier() * multiplicand); } /** * Multiplies this prefix by {@code other}. - * + * * @param other prefix to multiply by * @return product of prefixes * @since 2019-04-13 @@ -220,7 +219,7 @@ public final class UnitPrefix implements Nameable { /** * Raises this prefix to an exponent. - * + * * @param exponent exponent to raise to * @return result of exponentiation. * @since 2019-04-13 @@ -230,19 +229,16 @@ public final class UnitPrefix implements Nameable { return valueOf(Math.pow(this.getMultiplier(), exponent)); } - /** - * @return a string describing the prefix and its multiplier - */ + /** @return a string describing the prefix and its multiplier */ @Override public String toString() { if (this.getPrimaryName().isPresent()) return String.format("%s (\u00D7 %s)", this.getPrimaryName().get(), this.multiplier); - else if (this.getSymbol().isPresent()) + if (this.getSymbol().isPresent()) return String.format("%s (\u00D7 %s)", this.getSymbol().get(), this.multiplier); - else - return String.format("Unit Prefix (\u00D7 %s)", this.multiplier); + return String.format("Unit Prefix (\u00D7 %s)", this.multiplier); } /** diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java index a331d3d..b195f13 100644 --- a/src/main/java/sevenUnits/unit/UnitType.java +++ b/src/main/java/sevenUnits/unit/UnitType.java @@ -57,9 +57,8 @@ public enum UnitType { public static final UnitType getType(Unit u, Predicate isSemiMetric) { if (isSemiMetric.test(u)) return SEMI_METRIC; - else if (u.isMetric()) + if (u.isMetric()) return METRIC; - else - return NON_METRIC; + return NON_METRIC; } } diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index 4003c17..e24b6e2 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -17,16 +17,15 @@ package sevenUnits.unit; import java.util.Objects; -import java.util.Optional; import sevenUnits.utils.NameSymbol; /** * A value expressed in a unit. - * + * * Unless otherwise indicated, all methods in this class throw a * {@code NullPointerException} when an argument is null. - * + * * @author Adrien Hopkins * @since 2020-07-26 * @since v0.3.0 @@ -34,7 +33,7 @@ import sevenUnits.utils.NameSymbol; public final class UnitValue { /** * Creates a {@code UnitValue} from a unit and the associated value. - * + * * @param unit unit to use * @param value value to use * @return {@code UnitValue} instance @@ -62,18 +61,18 @@ public final class UnitValue { * @since 2020-10-01 * @since v0.3.0 */ - public final boolean canConvertTo(Unit other) { + public boolean canConvertTo(Unit other) { return this.unit.canConvertTo(other); } /** * Returns a UnitValue that represents the same value expressed in a * different unit - * + * * @param other new unit to express value in * @return value expressed in {@code other} */ - public final UnitValue convertTo(Unit other) { + public UnitValue convertTo(Unit other) { return UnitValue.of(other, this.getUnit().convertTo(other, this.getValue())); } @@ -88,8 +87,8 @@ public final class UnitValue { * @since 2020-09-29 * @since v0.3.0 */ - public final LinearUnitValue convertToBase(NameSymbol ns) { - final LinearUnit base = LinearUnit.getBase(this.unit).withName(ns); + public LinearUnitValue convertToBase(NameSymbol ns) { + final var base = LinearUnit.getBase(this.unit).withName(ns); return this.convertToLinear(base); } @@ -100,7 +99,7 @@ public final class UnitValue { * @since 2020-09-29 * @since v0.3.0 */ - public final LinearUnitValue convertToLinear(LinearUnit newUnit) { + public LinearUnitValue convertToLinear(LinearUnit newUnit) { return LinearUnitValue.getExact(newUnit, this.getUnit().convertTo(newUnit, this.getValue())); } @@ -114,7 +113,7 @@ public final class UnitValue { public boolean equals(Object obj) { if (!(obj instanceof UnitValue)) return false; - final UnitValue other = (UnitValue) obj; + final var other = (UnitValue) obj; return Objects.equals(this.getUnit().getBase(), other.getUnit().getBase()) && Double.doubleToLongBits( this.getUnit().convertToBase(this.getValue())) == Double @@ -127,7 +126,7 @@ public final class UnitValue { * @since 2020-09-29 * @since v0.3.0 */ - public final Unit getUnit() { + public Unit getUnit() { return this.unit; } @@ -136,7 +135,7 @@ public final class UnitValue { * @since 2020-09-29 * @since v0.3.0 */ - public final double getValue() { + public double getValue() { return this.value; } @@ -148,16 +147,15 @@ public final class UnitValue { @Override public String toString() { - final Optional primaryName = this.getUnit().getPrimaryName(); - final Optional symbol = this.getUnit().getSymbol(); + final var primaryName = this.getUnit().getPrimaryName(); + final var symbol = this.getUnit().getSymbol(); if (primaryName.isEmpty() && symbol.isEmpty()) { - final double baseValue = this.getUnit().convertToBase(this.getValue()); + final var baseValue = this.getUnit().convertToBase(this.getValue()); return String.format("%s unnamed unit (= %s %s)", this.getValue(), baseValue, this.getUnit().getBase() .toString(unit -> unit.getSymbol().orElseThrow())); - } else { - final String unitName = symbol.orElse(primaryName.get()); - return this.getValue() + " " + unitName; } + final var unitName = symbol.orElse(primaryName.get()); + return this.getValue() + " " + unitName; } } diff --git a/src/main/java/sevenUnits/unit/package-info.java b/src/main/java/sevenUnits/unit/package-info.java index 6d867d3..c650b58 100644 --- a/src/main/java/sevenUnits/unit/package-info.java +++ b/src/main/java/sevenUnits/unit/package-info.java @@ -16,7 +16,7 @@ */ /** * Everything to do with the units that make up Unit Converter. - * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.1.0 diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java index dd21a22..6244ee6 100644 --- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java +++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java @@ -49,8 +49,8 @@ import java.util.function.Predicate; * Other than that, the only difference between the provided collections and * the returned collections are that elements don't exist if they don't pass the * provided condition. - * - * + * + * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 @@ -58,7 +58,7 @@ import java.util.function.Predicate; public final class ConditionalExistenceCollections { /** * Elements in this collection only exist if they meet a condition. - * + * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 @@ -71,7 +71,7 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalExistenceCollection}. - * + * * @param collection * @param existenceCondition * @since 2019-10-17 @@ -103,7 +103,7 @@ public final class ConditionalExistenceCollections { // instance of E // therefore this cast will always work @SuppressWarnings("unchecked") - final E e = (E) o; + final var e = (E) o; return this.existenceCondition.test(e); } @@ -118,7 +118,7 @@ public final class ConditionalExistenceCollections { public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not // execute - final boolean containedObject = this.contains(o); + final var containedObject = this.contains(o); return this.collection.remove(o) && containedObject; } @@ -149,7 +149,7 @@ public final class ConditionalExistenceCollections { /** * Elements in this wrapper iterator only exist if they pass a condition. - * + * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 @@ -163,7 +163,7 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalExistenceIterator}. - * + * * @param iterator * @param condition * @since 2019-10-17 @@ -178,7 +178,7 @@ public final class ConditionalExistenceCollections { /** * Gets the next element, and sets nextElement and hasNext accordingly. - * + * * @since 2019-10-17 * @since v0.3.0 */ @@ -202,11 +202,11 @@ public final class ConditionalExistenceCollections { @Override public E next() { if (this.hasNext()) { - final E next = this.nextElement; + final var next = this.nextElement; this.getAndSetNextElement(); return next; - } else - throw new NoSuchElementException(); + } + throw new NoSuchElementException(); } @Override @@ -217,7 +217,7 @@ public final class ConditionalExistenceCollections { /** * Mappings in this map only exist if the entry passes some condition. - * + * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 @@ -230,7 +230,7 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalExistenceMap}. - * + * * @param map * @param entryExistenceCondition * @since 2019-10-17 @@ -250,10 +250,10 @@ public final class ConditionalExistenceCollections { // 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; + final var keyAsK = (K) key; // get and test entry - final V value = this.map.get(key); + final var value = this.map.get(key); final Entry entry = new SimpleEntry<>(keyAsK, value); return this.entryExistenceCondition.test(entry); } @@ -269,7 +269,7 @@ public final class ConditionalExistenceCollections { return this.containsKey(key) ? this.map.get(key) : null; } - private final Entry getEntry(K key) { + private Entry getEntry(K key) { return new Entry<>() { @Override public K getKey() { @@ -296,7 +296,7 @@ public final class ConditionalExistenceCollections { @Override public V put(final K key, final V value) { - final V oldValue = this.map.put(key, value); + final var oldValue = this.map.put(key, value); // get and test entry final Entry entry = new SimpleEntry<>(key, oldValue); @@ -305,7 +305,7 @@ public final class ConditionalExistenceCollections { @Override public V remove(final Object key) { - final V oldValue = this.map.remove(key); + final var oldValue = this.map.remove(key); return this.containsKey(key) ? oldValue : null; } @@ -318,7 +318,7 @@ public final class ConditionalExistenceCollections { /** * Elements in this set only exist if a certain condition is true. - * + * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 @@ -330,7 +330,7 @@ public final class ConditionalExistenceCollections { /** * Creates the {@code ConditionalNonexistenceSet}. - * + * * @param set set to use * @param existenceCondition condition where element exists * @since 2019-10-17 @@ -368,7 +368,7 @@ public final class ConditionalExistenceCollections { // of E // therefore this cast will always work @SuppressWarnings("unchecked") - final E e = (E) o; + final var e = (E) o; return this.existenceCondition.test(e); } @@ -383,7 +383,7 @@ public final class ConditionalExistenceCollections { public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not // execute - final boolean containedObject = this.contains(o); + final var containedObject = this.contains(o); return this.set.remove(o) && containedObject; } @@ -414,7 +414,7 @@ public final class ConditionalExistenceCollections { /** * Elements in the returned wrapper collection are ignored if they don't pass * a condition. - * + * * @param type of elements in collection * @param collection collection to wrap * @param existenceCondition elements only exist if this returns true @@ -422,7 +422,7 @@ public final class ConditionalExistenceCollections { * @since 2019-10-17 * @since v0.3.0 */ - public static final Collection conditionalExistenceCollection( + public static Collection conditionalExistenceCollection( final Collection collection, final Predicate existenceCondition) { return new ConditionalExistenceCollection<>(collection, @@ -432,7 +432,7 @@ public final class ConditionalExistenceCollections { /** * Elements in the returned wrapper iterator are ignored if they don't pass a * condition. - * + * * @param type of elements in iterator * @param iterator iterator to wrap * @param existenceCondition elements only exist if this returns true @@ -440,7 +440,7 @@ public final class ConditionalExistenceCollections { * @since 2019-10-17 * @since v0.3.0 */ - public static final Iterator conditionalExistenceIterator( + public static Iterator conditionalExistenceIterator( final Iterator iterator, final Predicate existenceCondition) { return new ConditionalExistenceIterator<>(iterator, existenceCondition); } @@ -448,7 +448,7 @@ public final class ConditionalExistenceCollections { /** * Mappings in the returned wrapper map are ignored if the corresponding * entry doesn't pass a condition - * + * * @param type of key in map * @param type of value in map * @param map map to wrap @@ -457,8 +457,7 @@ public final class ConditionalExistenceCollections { * @since 2019-10-17 * @since v0.3.0 */ - public static final Map conditionalExistenceMap( - final Map map, + public static Map conditionalExistenceMap(final Map map, final Predicate> entryExistenceCondition) { return new ConditionalExistenceMap<>(map, entryExistenceCondition); } @@ -466,7 +465,7 @@ public final class ConditionalExistenceCollections { /** * Elements in the returned wrapper set are ignored if they don't pass a * condition. - * + * * @param type of elements in set * @param set set to wrap * @param existenceCondition elements only exist if this returns true @@ -474,7 +473,7 @@ public final class ConditionalExistenceCollections { * @since 2019-10-17 * @since v0.3.0 */ - public static final Set conditionalExistenceSet(final Set set, + public static Set conditionalExistenceSet(final Set set, final Predicate existenceCondition) { return new ConditionalExistenceSet<>(set, existenceCondition); } diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java index c7564c4..1366fd3 100644 --- a/src/main/java/sevenUnits/utils/DecimalComparison.java +++ b/src/main/java/sevenUnits/utils/DecimalComparison.java @@ -20,7 +20,7 @@ import java.math.BigDecimal; /** * A class that contains methods to compare float and double values. - * + * * @author Adrien Hopkins * @since 2019-03-18 * @since v0.2.0 @@ -29,7 +29,7 @@ public final class DecimalComparison { /** * The value used for double comparison. If two double values are within this * value multiplied by the larger value, they are considered equal. - * + * * @since 2019-03-18 * @since v0.2.0 */ @@ -38,7 +38,7 @@ public final class DecimalComparison { /** * The value used for float comparison. If two float values are within this * value multiplied by the larger value, they are considered equal. - * + * * @since 2019-03-18 * @since v0.2.0 */ @@ -63,20 +63,20 @@ public final class DecimalComparison { *
  • Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * - * + * * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final double a, final double b) { + public static boolean equals(final double a, final double b) { return DecimalComparison.equals(a, b, DOUBLE_EPSILON); } /** * Tests for double equality using a custom epsilon value. - * + * *

    * WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -93,7 +93,7 @@ public final class DecimalComparison { *

  • Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * - * + * * @param a first value to test * @param b second value to test * @param epsilon allowed difference @@ -101,14 +101,14 @@ public final class DecimalComparison { * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final double a, final double b, + public static boolean equals(final double a, final double b, 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}. - * + * *

    * WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -125,20 +125,20 @@ public final class DecimalComparison { *

  • Use {@link BigDecimal} instead of {@code float} (this will make a * violation of transitivity 100% impossible) * - * + * * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final float a, final float b) { + public static boolean equals(final float a, final float b) { return DecimalComparison.equals(a, b, FLOAT_EPSILON); } /** * Tests for float equality using a custom epsilon value. - * + * *

    * WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -155,7 +155,7 @@ public final class DecimalComparison { *

  • Use {@link BigDecimal} instead of {@code float} (this will make a * violation of transitivity 100% impossible) * - * + * * @param a first value to test * @param b second value to test * @param epsilon allowed difference @@ -163,7 +163,7 @@ public final class DecimalComparison { * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final float a, final float b, + public static boolean equals(final float a, final float b, final float epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } @@ -188,14 +188,14 @@ public final class DecimalComparison { *
  • Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * - * + * * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2020-09-07 * @since v0.3.0 */ - public static final boolean equals(final UncertainDouble a, + public static boolean equals(final UncertainDouble a, final UncertainDouble b) { return DecimalComparison.equals(a.value(), b.value()) && DecimalComparison.equals(a.uncertainty(), b.uncertainty()); @@ -203,7 +203,7 @@ public final class DecimalComparison { /** * Tests for {@code UncertainDouble} equality using a custom epsilon value. - * + * *

    * WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, @@ -220,7 +220,7 @@ public final class DecimalComparison { *

  • Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) * - * + * * @param a first value to test * @param b second value to test * @param epsilon allowed difference @@ -228,7 +228,7 @@ public final class DecimalComparison { * @since 2019-03-18 * @since v0.2.0 */ - public static final boolean equals(final UncertainDouble a, + public static boolean equals(final UncertainDouble a, final UncertainDouble b, final double epsilon) { return DecimalComparison.equals(a.value(), b.value(), epsilon) && DecimalComparison.equals(a.uncertainty(), b.uncertainty(), diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 783a135..03c763c 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -31,7 +31,7 @@ import java.util.function.UnaryOperator; /** * An object that can parse expressions with unary or binary operators. - * + * * @author Adrien Hopkins * @param type of object that exists in parsed expressions * @since 2019-03-14 @@ -40,7 +40,7 @@ import java.util.function.UnaryOperator; public final class ExpressionParser { /** * A builder that can create {@code ExpressionParser} instances. - * + * * @author Adrien Hopkins * @param type of object that exists in parsed expressions * @since 2019-03-17 @@ -51,49 +51,49 @@ public final class ExpressionParser { * A function that obtains a parseable object from a string. For example, * an integer {@code ExpressionParser} would use * {@code Integer::parseInt}. - * + * * @since 2019-03-14 * @since v0.2.0 */ private final Function objectObtainer; - + /** * The function of the space as an operator (like 3 x y) - * + * * @since 2019-03-22 * @since v0.2.0 */ private String spaceFunction = null; - + /** * A map mapping operator strings to operator functions, for unary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ private final Map> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ private final Map> binaryOperators; - + /** * A map mapping operator strings to numeric functions. - * + * * @since 2024-03-23 * @since v0.5.0 */ private final Map> numericOperators; - + /** * Creates the {@code Builder}. - * + * * @param objectObtainer a function that can turn strings into objects of * the type handled by the parser. * @throws NullPointerException if {@code objectObtainer} is null @@ -107,10 +107,10 @@ public final class ExpressionParser { this.binaryOperators = new HashMap<>(); this.numericOperators = new HashMap<>(); } - + /** * Adds a binary operator to the builder. - * + * * @param text text used to reference the operator, like '+' * @param operator operator to add * @param priority operator's priority, which determines which operators @@ -125,7 +125,7 @@ public final class ExpressionParser { final BinaryOperator 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 priorityOperator = new PriorityBinaryOperator<>( @@ -134,16 +134,16 @@ public final class ExpressionParser { 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 @@ -155,7 +155,7 @@ public final class ExpressionParser { 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 priorityOperator = new PriorityBiFunction<>( @@ -164,16 +164,16 @@ public final class ExpressionParser { 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. - * + * * @param operator text of operator to use * @return this builder * @since 2019-03-22 @@ -181,18 +181,18 @@ public final class ExpressionParser { */ public Builder 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. - * + * * @param text text used to reference the operator, like '-' * @param operator operator to add * @param priority operator's priority, which determines which operators @@ -207,7 +207,7 @@ public final class ExpressionParser { final UnaryOperator 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 priorityOperator = new PriorityUnaryOperator<>( @@ -220,7 +220,7 @@ public final class ExpressionParser { this.unaryOperators.put(text, priorityOperator); return this; } - + /** * @return an {@code ExpressionParser} instance with the properties * given to this builder @@ -232,11 +232,11 @@ public final class ExpressionParser { this.binaryOperators, this.numericOperators, this.spaceFunction); } } - + /** * A binary operator with a priority field that determines which operators * apply first. - * + * * @author Adrien Hopkins * @param type of operand and result * @since 2019-03-17 @@ -247,15 +247,15 @@ public final class ExpressionParser { /** * 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 @@ -263,14 +263,14 @@ public final class ExpressionParser { public PriorityBiFunction(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. - * + * *

    * {@inheritDoc} *

    - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -278,12 +278,11 @@ public final class ExpressionParser { public int compareTo(final PriorityBiFunction o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } - + /** * @return priority * @since 2019-03-22 @@ -293,11 +292,11 @@ public final class ExpressionParser { return this.priority; } } - + /** * A binary operator with a priority field that determines which operators * apply first. - * + * * @author Adrien Hopkins * @param type of operand and result * @since 2019-03-17 @@ -308,15 +307,15 @@ public final class ExpressionParser { /** * 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 @@ -324,14 +323,14 @@ public final class ExpressionParser { public PriorityBinaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. - * + * *

    * {@inheritDoc} *

    - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -339,12 +338,11 @@ public final class ExpressionParser { public int compareTo(final PriorityBinaryOperator o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } - + /** * @return priority * @since 2019-03-22 @@ -354,11 +352,11 @@ public final class ExpressionParser { return this.priority; } } - + /** * A unary operator with a priority field that determines which operators * apply first. - * + * * @author Adrien Hopkins * @param type of operand and result * @since 2019-03-17 @@ -369,15 +367,15 @@ public final class ExpressionParser { /** * 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 PriorityUnaryOperator}. - * + * * @param priority operator's priority * @since 2019-03-17 * @since v0.2.0 @@ -385,14 +383,14 @@ public final class ExpressionParser { public PriorityUnaryOperator(final int priority) { this.priority = priority; } - + /** * Compares this object to another by priority. - * + * *

    * {@inheritDoc} *

    - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -400,12 +398,11 @@ public final class ExpressionParser { public int compareTo(final PriorityUnaryOperator o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } - + /** * @return priority * @since 2019-03-22 @@ -415,37 +412,37 @@ public final class ExpressionParser { return this.priority; } } - + /** * The types of tokens that are available. - * + * * @author Adrien Hopkins * @since 2019-03-14 * @since v0.2.0 */ - private static enum TokenType { + private enum TokenType { OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR; } - + /** * The opening bracket. - * + * * @since 2019-03-22 * @since v0.2.0 */ public static final char OPENING_BRACKET = '('; - + /** * The closing bracket. - * + * * @since 2019-03-22 * @since v0.2.0 */ public static final char CLOSING_BRACKET = ')'; - + /** * Finds the other bracket in a pair of brackets, given the position of one. - * + * * @param string string that contains brackets * @param bracketPosition position of first bracket * @return position of matching bracket @@ -456,9 +453,9 @@ public final class ExpressionParser { private static int findBracketPair(final String string, final int bracketPosition) { Objects.requireNonNull(string, "string must not be null."); - - final char openingBracket = string.charAt(bracketPosition); - + + final var openingBracket = string.charAt(bracketPosition); + // figure out what closing bracket to look for final char closingBracket; switch (openingBracket) { @@ -475,16 +472,16 @@ public final class ExpressionParser { 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; - + var bracketLevel = 0; + // iterate over the string to find the closing bracket - for (int currentPosition = bracketPosition; currentPosition < string + for (var currentPosition = bracketPosition; currentPosition < string .length(); currentPosition++) { - final char currentCharacter = string.charAt(currentPosition); - + final var currentCharacter = string.charAt(currentPosition); + if (currentCharacter == openingBracket) { bracketLevel++; } else if (currentCharacter == closingBracket) { @@ -493,55 +490,55 @@ public final class ExpressionParser { 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}. - * + * * @since 2019-03-14 * @since v0.2.0 */ private final Function objectObtainer; - + /** * A map mapping operator strings to operator functions, for unary operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ private final Map> unaryOperators; - + /** * A map mapping operator strings to operator functions, for binary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ private final Map> binaryOperators; - + /** * A map mapping operator strings to numeric functions. - * + * * @since 2024-03-23 * @since v0.5.0 */ private final Map> numericOperators; - + /** * The operator for space, or null if spaces have no function. - * + * * @since 2019-03-22 * @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 @@ -561,7 +558,7 @@ public final class ExpressionParser { this.numericOperators = numericOperators; this.spaceOperator = spaceOperator; } - + /** * Converts a given mathematical expression to reverse Polish notation * (operators after operands). @@ -570,7 +567,7 @@ public final class ExpressionParser { * {@code 2 * (3 + 4)}
    * becomes
    * {@code 2 3 4 + *}. - * + * * @param expression expression * @return expression in RPN * @throws IllegalArgumentException if expression is invalid (e.g. @@ -580,25 +577,25 @@ public final class ExpressionParser { */ String convertExpressionToReversePolish(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final List components = new ArrayList<>(); - + // the part of the expression remaining to parse - String partialExpression = expression; - + var partialExpression = expression; + // find and deal with brackets while (partialExpression.indexOf(OPENING_BRACKET) != -1) { - final int openingBracketPosition = partialExpression + final var openingBracketPosition = partialExpression .indexOf(OPENING_BRACKET); - final int closingBracketPosition = findBracketPair(partialExpression, + final var closingBracketPosition = findBracketPair(partialExpression, openingBracketPosition); - + // check for function if (openingBracketPosition > 0 && partialExpression.charAt(openingBracketPosition - 1) != ' ') { // function like sin(2) or tempF(32) // find the position of the last space - int spacePosition = openingBracketPosition; + var spacePosition = openingBracketPosition; while (spacePosition >= 0 && partialExpression.charAt(spacePosition) != ' ') { spacePosition--; @@ -609,8 +606,6 @@ public final class ExpressionParser { .substring(0, spacePosition + 1).split(" "))); components.add(partialExpression.substring(spacePosition + 1, closingBracketPosition + 1)); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); } else { // normal brackets like (1 + 2) * (3 / 5) components.addAll(Arrays.asList(partialExpression @@ -618,37 +613,37 @@ public final class ExpressionParser { components.add(this.convertExpressionToReversePolish( partialExpression.substring(openingBracketPosition + 1, closingBracketPosition))); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); } + partialExpression = partialExpression + .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++) { + for (var i = 0; i < components.size() - 1; i++) { if (this.getTokenType(components.get(i)) == TokenType.OBJECT && this .getTokenType(components.get(i + 1)) == TokenType.OBJECT) { components.add(++i, this.spaceOperator); } } } - + // turn the expression into reverse Polish while (true) { - final int highestPriorityOperatorPosition = this + final var highestPriorityOperatorPosition = this .findHighestPriorityOperatorPosition(components); if (highestPriorityOperatorPosition == -1) { break; } - + // swap components based on what kind of operator there is // 1 + 2 becomes 2 1 + // - 1 becomes 1 - @@ -658,9 +653,9 @@ public final class ExpressionParser { if (components.size() < 2) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); - final String unaryOperator = components + final var unaryOperator = components .remove(highestPriorityOperatorPosition); - final String operand = components + final var operand = components .remove(highestPriorityOperatorPosition); components.add(highestPriorityOperatorPosition, operand + " " + unaryOperator); @@ -670,11 +665,11 @@ public final class ExpressionParser { if (components.size() < 3) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); - final String binaryOperator = components + final var binaryOperator = components .remove(highestPriorityOperatorPosition); - final String operand1 = components + final var operand1 = components .remove(highestPriorityOperatorPosition - 1); - final String operand2 = components + final var operand2 = components .remove(highestPriorityOperatorPosition - 1); components.add(highestPriorityOperatorPosition - 1, operand1 + " " + operand2 + " " + binaryOperator); @@ -683,7 +678,7 @@ public final class ExpressionParser { throw new AssertionError("Expected operator, found non-operator."); } } - + // join all of the components together, then ensure there is only one // space in a row if (components.size() != 1) @@ -691,10 +686,10 @@ public final class ExpressionParser { "Invalid expression \"" + expression + "\"."); return components.get(0).replaceAll(" +", " ").trim(); } - + /** * Finds the position of the highest-priority operator in a list - * + * * @param components components to test * @param blacklist positions of operators that should be ignored * @return position of highest priority, or -1 if the list contains no @@ -707,40 +702,40 @@ public final class ExpressionParser { final List components) { Objects.requireNonNull(components, "components must not be null."); // find highest priority - int maxPriority = Integer.MIN_VALUE; - int maxPriorityPosition = -1; - + var maxPriority = Integer.MIN_VALUE; + var 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++) { - + for (var i = 0; i < components.size(); i++) { + switch (this.getTokenType(components.get(i))) { case UNARY_OPERATOR: - final PriorityUnaryOperator unaryOperator = this.unaryOperators + final var unaryOperator = this.unaryOperators .get(components.get(i)); - final int unaryPriority = unaryOperator.getPriority(); - + final var unaryPriority = unaryOperator.getPriority(); + if (unaryPriority > maxPriority) { maxPriority = unaryPriority; maxPriorityPosition = i; } break; case BINARY_OPERATOR: - final PriorityBinaryOperator binaryOperator = this.binaryOperators + final var binaryOperator = this.binaryOperators .get(components.get(i)); - final int binaryPriority = binaryOperator.getPriority(); - + final var binaryPriority = binaryOperator.getPriority(); + if (binaryPriority > maxPriority) { maxPriority = binaryPriority; maxPriorityPosition = i; } break; case NUMERIC_OPERATOR: - final PriorityBiFunction numericOperator = this.numericOperators + final var numericOperator = this.numericOperators .get(components.get(i)); - final int numericPriority = numericOperator.getPriority(); - + final var numericPriority = numericOperator.getPriority(); + if (numericPriority > maxPriority) { maxPriority = numericPriority; maxPriorityPosition = i; @@ -750,14 +745,14 @@ public final class ExpressionParser { break; } } - + // max priority position found return maxPriorityPosition; } - + /** * Determines whether an inputted string is an object or an operator - * + * * @param token string to input * @return type of token it is * @throws NullPointerException if {@code expression} is null @@ -766,20 +761,19 @@ public final class ExpressionParser { */ 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)) + if (this.binaryOperators.containsKey(token)) return TokenType.BINARY_OPERATOR; - else if (this.numericOperators.containsKey(token)) + if (this.numericOperators.containsKey(token)) return TokenType.NUMERIC_OPERATOR; - else - return TokenType.OBJECT; + return TokenType.OBJECT; } - + /** * Parses an expression. - * + * * @param expression expression to parse * @return result * @throws NullPointerException if {@code expression} is null @@ -790,10 +784,10 @@ public final class ExpressionParser { return this.parseReversePolishExpression( this.convertExpressionToReversePolish(expression)); } - + /** * Parses an expression expressed in reverse Polish notation. - * + * * @param expression expression to parse * @return result * @throws NullPointerException if {@code expression} is null @@ -802,43 +796,43 @@ public final class ExpressionParser { */ T parseReversePolishExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - + final Deque stack = new ArrayDeque<>(); final Deque 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 var o1 = stack.pop(); + final var o2 = stack.pop(); final BinaryOperator binaryOperator = this.binaryOperators .get(item); - + stack.push(binaryOperator.apply(o2, o1)); 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 var ot = stack.pop(); + final var on = doubleStack.pop(); final BiFunction op = this.numericOperators .get(item); stack.push(op.apply(ot, on)); break; - + case OBJECT: // just add it to the stack // these try-catch statements are necessary @@ -860,33 +854,33 @@ public final class ExpressionParser { } } 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 var o = stack.pop(); final UnaryOperator 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( "Computation ended up with more than one answer."); - else if (stack.size() == 0) + if (stack.size() == 0) throw new IllegalStateException( "Computation ended up without an answer."); return stack.pop(); diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index ebb1e8b..089978b 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -19,14 +19,13 @@ package sevenUnits.utils; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Set; /** * A class that can be used to specify names and a symbol for a unit. - * + * * @author Adrien Hopkins * @since 2019-10-21 * @since v0.3.0 @@ -39,16 +38,16 @@ public final class NameSymbol { /** * Creates a {@code NameSymbol}, ensuring that if primaryName is null and * otherNames is not empty, one name is moved from otherNames to primaryName - * + * * Ensure that otherNames is not a copy of the inputted argument. */ - private static final NameSymbol create(final String name, - final String symbol, final Set otherNames) { + private static NameSymbol create(final String name, final String symbol, + final Set otherNames) { final Optional primaryName; if (name == null && !otherNames.isEmpty()) { // get primary name and remove it from savedNames - final Iterator it = otherNames.iterator(); + final var it = otherNames.iterator(); assert it.hasNext(); primaryName = Optional.of(it.next()); otherNames.remove(primaryName.get()); @@ -63,7 +62,7 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a primary name, a symbol and no other * names. - * + * * @param name name to use * @param symbol symbol to use * @return NameSymbol instance @@ -71,7 +70,7 @@ public final class NameSymbol { * @since v0.3.0 * @throws NullPointerException if name or symbol is null */ - public static final NameSymbol of(final String name, final String symbol) { + public static NameSymbol of(final String name, final String symbol) { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>()); } @@ -79,7 +78,7 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use @@ -88,7 +87,7 @@ public final class NameSymbol { * @since v0.3.0 * @throws NullPointerException if any argument is null */ - public static final NameSymbol of(final String name, final String symbol, + public static NameSymbol of(final String name, final String symbol, final Set otherNames) { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>(Objects.requireNonNull(otherNames, @@ -98,7 +97,7 @@ public final class NameSymbol { /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use @@ -117,14 +116,14 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a primary name, no symbol, and no other * names. - * + * * @param name name to use * @return NameSymbol instance * @since 2019-10-21 * @since v0.3.0 * @throws NullPointerException if name is null */ - public static final NameSymbol ofName(final String name) { + public static NameSymbol ofName(final String name) { return new NameSymbol(Optional.of(name), Optional.empty(), new HashSet<>()); } @@ -139,7 +138,7 @@ public final class NameSymbol { * If {@code name} is null and {@code otherNames} is not empty, a primary * name will be picked from {@code otherNames}. This name will not appear in * getOtherNames(). - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use @@ -147,8 +146,8 @@ public final class NameSymbol { * @since 2019-11-26 * @since v0.3.0 */ - public static final NameSymbol ofNullable(final String name, - final String symbol, final Set otherNames) { + public static NameSymbol ofNullable(final String name, final String symbol, + final Set otherNames) { return NameSymbol.create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); } @@ -163,7 +162,7 @@ public final class NameSymbol { * If {@code name} is null and {@code otherNames} is not empty, a primary * name will be picked from {@code otherNames}. This name will not appear in * getOtherNames(). - * + * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use @@ -179,14 +178,14 @@ public final class NameSymbol { /** * Gets a {@code NameSymbol} with a symbol and no names. - * + * * @param symbol symbol to use * @return NameSymbol instance * @since 2019-10-21 * @since v0.3.0 * @throws NullPointerException if symbol is null */ - public static final NameSymbol ofSymbol(final String symbol) { + public static NameSymbol ofSymbol(final String symbol) { return new NameSymbol(Optional.empty(), Optional.of(symbol), new HashSet<>()); } @@ -198,7 +197,7 @@ public final class NameSymbol { /** * Creates the {@code NameSymbol}. - * + * * @param primaryName primary name of unit * @param symbol symbol used to represent unit * @param otherNames other names and/or spellings, should be a mutable copy @@ -206,8 +205,8 @@ public final class NameSymbol { * @since 2019-10-21 * @since v0.3.0 */ - NameSymbol(final Optional primaryName, - final Optional symbol, final Set otherNames) { + NameSymbol(final Optional primaryName, final Optional symbol, + final Set otherNames) { this.primaryName = primaryName; this.symbol = symbol; if (otherNames != null) { @@ -228,7 +227,7 @@ public final class NameSymbol { return true; if (!(obj instanceof NameSymbol)) return false; - final NameSymbol other = (NameSymbol) obj; + final var other = (NameSymbol) obj; if (this.otherNames == null) { if (other.otherNames != null) return false; @@ -252,7 +251,7 @@ public final class NameSymbol { * @since 2019-10-21 * @since v0.3.0 */ - public final Set getOtherNames() { + public Set getOtherNames() { return this.otherNames; } @@ -261,7 +260,7 @@ public final class NameSymbol { * @since 2019-10-21 * @since v0.3.0 */ - public final Optional getPrimaryName() { + public Optional getPrimaryName() { return this.primaryName; } @@ -270,14 +269,14 @@ public final class NameSymbol { * @since 2019-10-21 * @since v0.3.0 */ - public final Optional getSymbol() { + public Optional getSymbol() { return this.symbol; } @Override public int hashCode() { - final int prime = 31; - int result = 1; + final var prime = 31; + var result = 1; result = prime * result + (this.otherNames == null ? 0 : this.otherNames.hashCode()); result = prime * result @@ -287,10 +286,8 @@ public final class NameSymbol { return result; } - /** - * @return true iff this {@code NameSymbol} contains no names or symbols. - */ - public final boolean isEmpty() { + /** @return true iff this {@code NameSymbol} contains no names or symbols. */ + public boolean isEmpty() { // if primaryName is empty, otherNames must also be empty return this.primaryName.isEmpty() && this.symbol.isEmpty(); } @@ -299,31 +296,30 @@ public final class NameSymbol { public String toString() { if (this.isEmpty()) return "NameSymbol.EMPTY"; - else if (this.primaryName.isPresent() && this.symbol.isPresent()) + if (this.primaryName.isPresent() && this.symbol.isPresent()) return this.primaryName.orElseThrow() + " (" + this.symbol.orElseThrow() + ")"; - else - return this.primaryName.orElseGet(this.symbol::orElseThrow); + 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 * name will become an other name, otherwise it will become the primary name. - * + * * @param name additional name to add * @return copy of this NameSymbol with the additional name * * @since 2022-04-19 * @since v0.4.0 */ - public final NameSymbol withExtraName(String name) { + public NameSymbol withExtraName(String name) { if (this.primaryName.isPresent()) { final var otherNames = new HashSet<>(this.otherNames); otherNames.add(name); return NameSymbol.ofNullable(this.primaryName.orElse(null), this.symbol.orElse(null), otherNames); - } else - return NameSymbol.ofNullable(name, this.symbol.orElse(null)); + } + return NameSymbol.ofNullable(name, this.symbol.orElse(null)); } } \ No newline at end of file diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index efd1ab8..166de55 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -34,7 +34,7 @@ public interface Nameable { * @since v0.4.0 */ default String getName() { - final NameSymbol ns = this.getNameSymbol(); + final var ns = this.getNameSymbol(); return ns.getPrimaryName().or(ns::getSymbol).orElse("Unnamed"); } @@ -71,7 +71,7 @@ public interface Nameable { * @since v0.4.0 */ default String getShortName() { - final NameSymbol ns = this.getNameSymbol(); + final var ns = this.getNameSymbol(); return ns.getSymbol().or(ns::getPrimaryName).orElse("Unnamed"); } diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 1b8832e..23fe41c 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -29,7 +29,7 @@ import java.util.function.Function; /** * An immutable product of multiple objects of a type, such as base units. The * objects can be multiplied and exponentiated. - * + * * @author Adrien Hopkins * @param type of object that is being multiplied * @since 2019-10-16 @@ -41,10 +41,10 @@ public class ObjectProduct implements Nameable { * this value, a warning will be printed to standard error. */ private static final double ROUND_WARN_THRESHOLD = 1e-12; - + /** * Returns an empty ObjectProduct of a certain type - * + * * @param type of objects that can be multiplied * @return empty product * @since 2019-10-16 @@ -53,10 +53,10 @@ public class ObjectProduct implements Nameable { public static final ObjectProduct empty() { return new ObjectProduct<>(new HashMap<>()); } - + /** * Gets an {@code ObjectProduct} from an object-to-integer mapping - * + * * @param type of object in product * @param map map mapping objects to exponents * @return object product @@ -67,13 +67,13 @@ public class ObjectProduct implements Nameable { final Map map) { return new ObjectProduct<>(new HashMap<>(map)); } - + /** * Gets an ObjectProduct that has one of the inputted argument, and nothing * else. - * + * * @param object object that will be in the product - * @param type of object contained in returned ObjectProduct + * @param type of object contained in returned ObjectProduct * @return product * @since 2019-10-16 * @since v0.3.0 @@ -85,24 +85,22 @@ public class ObjectProduct 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. - * + * * @since 2019-10-16 * @since v0.3.0 */ final Map exponents; - - /** - * The object's name and symbol - */ + + /** The object's name and symbol */ private final NameSymbol nameSymbol; - + /** * Creates a {@code ObjectProduct} without a name/symbol. - * + * * @param exponents objects that make up this product * @since 2019-10-16 * @since v0.3.0 @@ -110,10 +108,10 @@ public class ObjectProduct implements Nameable { ObjectProduct(final Map exponents) { this(exponents, NameSymbol.EMPTY); } - + /** * Creates the {@code ObjectProduct}. - * + * * @param exponents objects that make up this product * @param nameSymbol name and symbol of object product * @since 2019-10-16 @@ -125,7 +123,7 @@ public class ObjectProduct implements Nameable { e -> !Integer.valueOf(0).equals(e.getValue()))); this.nameSymbol = nameSymbol; } - + /** * Calculates the quotient of two products * @@ -141,17 +139,17 @@ public class ObjectProduct implements Nameable { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) - other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + // this method relies on the use of ZeroIsNullMap @Override public boolean equals(final Object obj) { @@ -162,7 +160,7 @@ public class ObjectProduct 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 @@ -171,7 +169,7 @@ public class ObjectProduct implements Nameable { public Map exponentMap() { return this.exponents; } - + /** * @return a set of all of the base objects with non-zero exponents that make * up this dimension. @@ -180,7 +178,7 @@ public class ObjectProduct implements Nameable { */ public final Set getBaseSet() { final Set dimensions = new HashSet<>(); - + // add all dimensions with a nonzero exponent - zero exponents shouldn't // be there in the first place for (final T dimension : this.exponents.keySet()) { @@ -188,13 +186,13 @@ public class ObjectProduct implements Nameable { dimensions.add(dimension); } } - + return dimensions; } - + /** * Gets the exponent for a specific dimension. - * + * * @param dimension dimension to check * @return exponent for that dimension * @since 2018-12-12 @@ -203,17 +201,17 @@ public class ObjectProduct 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 @@ -221,8 +219,8 @@ public class ObjectProduct implements Nameable { * @since v0.3.0 */ public boolean isSingleObject() { - int oneCount = 0; - boolean twoOrMore = false; // has exponents of 2 or more + var oneCount = 0; + var twoOrMore = false; // has exponents of 2 or more for (final T b : this.getBaseSet()) { if (this.getExponent(b) == 1) { oneCount++; @@ -232,7 +230,7 @@ public class ObjectProduct implements Nameable { } return oneCount == 1 && !twoOrMore; } - + /** * Multiplies this product by another * @@ -248,20 +246,20 @@ public class ObjectProduct implements Nameable { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) + other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + /** * Returns this product, but to an exponent - * + * * @param exponent exponent * @return result of exponentiation * @since 2019-10-16 @@ -274,14 +272,14 @@ public class ObjectProduct implements Nameable { } return new ObjectProduct<>(map); } - + /** * Returns this product to an exponent, where every dimension is rounded to * the nearest integer. - * + * * This function will send a warning (via standard error) if the rounding * significantly changes the value. - * + * * @param exponent exponent to raise this product to * @return result of exponentiation * @@ -291,7 +289,7 @@ public class ObjectProduct implements Nameable { public ObjectProduct toExponentRounded(final double exponent) { final Map map = new HashMap<>(this.exponents); for (final T key : this.exponents.keySet()) { - final double newExponent = this.getExponent(key) * exponent; + final var newExponent = this.getExponent(key) * exponent; if (Math.abs( newExponent - Math.round(newExponent)) > ROUND_WARN_THRESHOLD) { System.err.printf( @@ -303,14 +301,14 @@ public class ObjectProduct implements Nameable { } return new ObjectProduct<>(map); } - + /** * Converts this product to a string using the objects' * {@link Object#toString()} method (or {@link Nameable#getShortName} if * available). If objects have a long toString representation, it is * recommended to use {@link #toString(Function)} instead to shorten the * returned string. - * + * *

    * {@inheritDoc} */ @@ -320,11 +318,11 @@ public class ObjectProduct 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} - * + * * @param objectToString function to convert objects to strings * @return string representation of product * @since 2019-10-16 @@ -333,7 +331,7 @@ public class ObjectProduct implements Nameable { public String toString(final Function objectToString) { final List positiveStringComponents = new ArrayList<>(); final List 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); @@ -347,15 +345,15 @@ public class ObjectProduct implements Nameable { objectToString.apply(object), -exponent)); } } - - final String positiveString = positiveStringComponents.isEmpty() ? "1" + + final var positiveString = positiveStringComponents.isEmpty() ? "1" : String.join(" * ", positiveStringComponents); - final String negativeString = negativeStringComponents.isEmpty() ? "" + final var negativeString = negativeStringComponents.isEmpty() ? "" : " / " + String.join(" * ", negativeStringComponents); - + return positiveString + negativeString; } - + /** * @param nameSymbol name to add to this product * @return named version of this {@code ObjectProduct}, using data from diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index cde3d37..c081a25 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -136,7 +135,7 @@ public final class SemanticVersionNumber return true; if (!(obj instanceof Builder)) return false; - final Builder other = (Builder) obj; + final var other = (Builder) obj; return Objects.equals(this.buildMetadata, other.buildMetadata) && this.major == other.major && this.minor == other.minor && this.patch == other.patch && Objects.equals( @@ -249,12 +248,11 @@ public final class SemanticVersionNumber public int compare(SemanticVersionNumber o1, SemanticVersionNumber o2) { Objects.requireNonNull(o1, "o1 may not be null"); Objects.requireNonNull(o2, "o2 may not be null"); - final int naturalComparison = o1.compareTo(o2); + final var naturalComparison = o1.compareTo(o2); if (naturalComparison == 0) return SemanticVersionNumber.compareIdentifiers(o1.buildMetadata, o2.buildMetadata); - else - return naturalComparison; + return naturalComparison; }; }; @@ -283,8 +281,8 @@ public final class SemanticVersionNumber * @since 2022-02-19 * @since v0.4.0 */ - public static final SemanticVersionNumber.Builder builder(int major, - int minor, int patch) { + public static SemanticVersionNumber.Builder builder(int major, int minor, + int patch) { if (major < 0) throw new IllegalArgumentException( "Major version must be non-negative."); @@ -307,57 +305,54 @@ public final class SemanticVersionNumber * @since 2022-02-20 * @since v0.4.0 */ - private static final int compareIdentifiers(List a, List b) { + private static int compareIdentifiers(List a, List b) { // test pre-release size - final int aSize = a.size(); - final int bSize = b.size(); + final var aSize = a.size(); + final var bSize = b.size(); // no identifiers is greater than any identifiers if (aSize != 0 && bSize == 0) return -1; - else if (aSize == 0 && bSize != 0) + 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); + for (var i = 0; i < Math.min(aSize, bSize); i++) { + final var aElement = a.get(i); + final var 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) - return 1; - } else + if (!NUMERIC_IDENTIFER.matcher(bElement).matches()) // aElement is a number and bElement is not a number // by the rules, a goes before b return -1; - } else { - if (NUMERIC_IDENTIFER.matcher(bElement).matches()) - // aElement is not a number but bElement is - // by the rules, a goes after b + // both are numbers, compare them + final var aNumber = Integer.parseInt(aElement); + final var bNumber = Integer.parseInt(bElement); + + if (aNumber < bNumber) + return -1; + else if (aNumber > bNumber) return 1; - else { - // both are not numbers, compare them - final int comparison = aElement.compareTo(bElement); - if (comparison != 0) - return comparison; - } + } else if (NUMERIC_IDENTIFER.matcher(bElement).matches()) + // aElement is not a number but bElement is + // by the rules, a goes after b + return 1; + else { + // both are not numbers, compare them + final var comparison = aElement.compareTo(bElement); + if (comparison != 0) + return comparison; } } - - // we just tested the stuff that's in common, maybe someone has more if (aSize < bSize) return -1; - else if (aSize > bSize) + if (aSize > bSize) return 1; - else - return 0; + return 0; + + // we just tested the stuff that's in common, maybe someone has more + } /** @@ -369,19 +364,19 @@ public final class SemanticVersionNumber * @since v0.4.0 * @see #toString */ - public static final SemanticVersionNumber fromString(String versionString) { + public static SemanticVersionNumber fromString(String versionString) { // parse & validate version string Objects.requireNonNull(versionString, "versionString may not be null"); - final Matcher m = VERSION_NUMBER.matcher(versionString); + final var m = VERSION_NUMBER.matcher(versionString); if (!m.matches()) 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)); + final var major = Integer.parseInt(m.group(1)); + final var minor = Integer.parseInt(m.group(2)); + final var patch = Integer.parseInt(m.group(3)); // pre release final List preRelease; @@ -406,13 +401,13 @@ public final class SemanticVersionNumber /** * Tests whether a string is a valid Semantic Version string - * + * * @param versionString string to test * @return true iff string is valid * @since 2022-02-19 * @since v0.4.0 */ - public static final boolean isValidVersionString(String versionString) { + public static boolean isValidVersionString(String versionString) { return VERSION_NUMBER.matcher(versionString).matches(); } @@ -432,7 +427,7 @@ public final class SemanticVersionNumber * @since 2022-02-19 * @since v0.4.0 */ - public static final SemanticVersionNumber preRelease(int major, int minor, + public static SemanticVersionNumber preRelease(int major, int minor, int patch, String preReleaseType, int preReleaseNumber) { if (major < 0) throw new IllegalArgumentException( @@ -470,7 +465,7 @@ public final class SemanticVersionNumber * @since 2022-02-19 * @since v0.4.0 */ - public static final SemanticVersionNumber stableVersion(int major, int minor, + public static SemanticVersionNumber stableVersion(int major, int minor, int patch) { if (major < 0) throw new IllegalArgumentException( @@ -536,17 +531,17 @@ public final class SemanticVersionNumber // test the three big numbers in order first if (this.major < o.major) return -1; - else if (this.major > o.major) + if (this.major > o.major) return 1; if (this.minor < o.minor) return -1; - else if (this.minor > o.minor) + if (this.minor > o.minor) return 1; if (this.patch < o.patch) return -1; - else if (this.patch > o.patch) + if (this.patch > o.patch) return 1; // now we just compare pre-release identifiers @@ -569,7 +564,7 @@ public final class SemanticVersionNumber * * If this function returns false, you may have to change your code to * upgrade it to {@code other} - * + * *

    * Two version numbers that are identical (ignoring build metadata) are * always compatible. Different version numbers are compatible as long as: @@ -601,17 +596,13 @@ public final class SemanticVersionNumber return true; if (!(obj instanceof SemanticVersionNumber)) return false; - final SemanticVersionNumber other = (SemanticVersionNumber) obj; + final var other = (SemanticVersionNumber) obj; if (this.buildMetadata == null) { if (other.buildMetadata != null) return false; } else if (!this.buildMetadata.equals(other.buildMetadata)) return false; - if (this.major != other.major) - return false; - if (this.minor != other.minor) - return false; - if (this.patch != other.patch) + if ((this.major != other.major) || (this.minor != other.minor) || (this.patch != other.patch)) return false; if (this.preReleaseIdentifiers == null) { if (other.preReleaseIdentifiers != null) @@ -624,8 +615,8 @@ public final class SemanticVersionNumber @Override public int hashCode() { - final int prime = 31; - int result = 1; + final var prime = 31; + var result = 1; result = prime * result + (this.buildMetadata == null ? 0 : this.buildMetadata.hashCode()); result = prime * result + this.major; @@ -697,13 +688,13 @@ public final class SemanticVersionNumber * For example, the version with major number 3, minor number 2, patch number * 1, pre-release identifiers "alpha" and "1" and build metadata "2022-02-19" * has a string representation "3.2.1-alpha.1+2022-02-19". - * + * * @since v0.4.0 * @see The official SemVer specification */ @Override public String toString() { - String versionString = String.format("%d.%d.%d", this.major, this.minor, + var versionString = String.format("%d.%d.%d", this.major, this.minor, this.patch); if (!this.preReleaseIdentifiers.isEmpty()) { versionString += "-" + String.join(".", this.preReleaseIdentifiers); diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index f700454..ecee586 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -19,7 +19,6 @@ package sevenUnits.utils; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Objects; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -32,16 +31,12 @@ import java.util.regex.Pattern; * @since v0.3.0 */ public final class UncertainDouble implements Comparable { - /** - * The exact value 0 - */ + /** 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 - */ + /** A regular expression that can recognize toString forms */ static final Pattern TO_STRING = Pattern.compile(NUMBER_REGEX // optional "± [number]" + "(?:\\s*(?:±|\\+-)\\s*" + NUMBER_REGEX + ")?"); @@ -50,18 +45,18 @@ public final class UncertainDouble implements Comparable { * 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. - * + * * @param s string to parse * @return parsed {@code UncertainDouble} - * + * * @throws NumberFormatException if the argument is not a number * * @since 2022-04-18 * @since v0.4.0 */ - public static final UncertainDouble fromRoundedString(String s) { - final BigDecimal value = new BigDecimal(s); - final double uncertainty = Math.pow(10, -value.scale()); + public static UncertainDouble fromRoundedString(String s) { + final var value = new BigDecimal(s); + final var uncertainty = Math.pow(10, -value.scale()); return UncertainDouble.of(value.doubleValue(), uncertainty); } @@ -72,16 +67,16 @@ public final class UncertainDouble implements Comparable { *

    * This method allows some alternative forms of the string representation, * such as using "+-" instead of "±". - * + * * @param s string to parse * @return {@code UncertainDouble} instance * @throws IllegalArgumentException if the string is invalid * @since 2020-09-07 * @since v0.3.0 */ - public static final UncertainDouble fromString(String s) { + public static UncertainDouble fromString(String s) { Objects.requireNonNull(s, "s may not be null"); - final Matcher matcher = TO_STRING.matcher(s); + final var matcher = TO_STRING.matcher(s); if (!matcher.matches()) throw new IllegalArgumentException( @@ -95,7 +90,7 @@ public final class UncertainDouble implements Comparable { "String " + s + " not in correct format."); } - final String uncertaintyString = matcher.group(2); + final var uncertaintyString = matcher.group(2); if (uncertaintyString == null) { uncertainty = 0; } else { @@ -113,32 +108,32 @@ public final class UncertainDouble implements Comparable { /** * Gets an {@code UncertainDouble} from its value and absolute * uncertainty. - * + * * @param value double's value * @param uncertainty double's uncertainty (non-negative) * @return {@code UncertainDouble} instance with these parameters - * + * * @since 2020-09-07 * @since v0.3.0 */ - public static final UncertainDouble of(double value, double uncertainty) { + public static UncertainDouble of(double value, double uncertainty) { return new UncertainDouble(value, uncertainty); } /** * Gets an {@code UncertainDouble} from its value and relative * uncertainty. - * + * * @param value double's value * @param relativeUncertainty double's uncertainty (non-negative); the * absolute uncertainty is equal to this value * multiplied by {@code relativeUncertainty} * @return {@code UncertainDouble} instance with these parameters - * + * * @since 2020-09-07 * @since v0.3.0 */ - public static final UncertainDouble ofRelative(double value, + public static UncertainDouble ofRelative(double value, double relativeUncertainty) { return new UncertainDouble(value, value * relativeUncertainty); } @@ -173,20 +168,20 @@ public final class UncertainDouble implements Comparable { * {@code false}. */ @Override - public final int compareTo(UncertainDouble o) { + public int compareTo(UncertainDouble o) { return Double.compare(this.value, o.value); } /** * Returns the quotient of {@code this} and {@code other}. - * + * * @param other number to divide by * @return quotient - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble dividedBy(UncertainDouble other) { + public UncertainDouble dividedBy(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.ofRelative(this.value / other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); @@ -194,27 +189,25 @@ public final class UncertainDouble implements Comparable { /** * Returns the quotient of {@code this} and the exact value {@code other}. - * + * * @param other number to divide by * @return quotient - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble dividedByExact(double other) { + public UncertainDouble dividedByExact(double other) { return UncertainDouble.of(this.value / other, this.uncertainty / other); } @Override - public final boolean equals(Object obj) { + public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof UncertainDouble)) return false; - final UncertainDouble other = (UncertainDouble) obj; - if (Double.compare(this.value, other.value) != 0) - return false; - if (Double.compare(this.uncertainty, other.uncertainty) != 0) + final var other = (UncertainDouble) obj; + if ((Double.compare(this.value, other.value) != 0) || (Double.compare(this.uncertainty, other.uncertainty) != 0)) return false; return true; } @@ -226,7 +219,7 @@ public final class UncertainDouble implements Comparable { * @since 2020-09-07 * @since v0.3.0 */ - public final boolean equivalent(UncertainDouble other) { + public boolean equivalent(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return Math.abs(this.value - other.value) <= Math.min(this.uncertainty, other.uncertainty); @@ -234,11 +227,11 @@ public final class UncertainDouble implements Comparable { /** * Gets the preferred scale for rounding a value for toString. - * + * * @since 2020-09-07 * @since v0.3.0 */ - private final int getDisplayScale() { + private int getDisplayScale() { // round based on uncertainty // if uncertainty starts with 1 (ignoring zeroes and the decimal // point), rounds @@ -246,24 +239,23 @@ public final class UncertainDouble implements Comparable { // otherwise, rounds so that uncertainty has 1 significant digits. // the value is rounded to the same number of decimal places as the // uncertainty. - final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); + final var bigUncertainty = BigDecimal.valueOf(this.uncertainty); // the scale that will give the uncertainty two decimal places - final int twoDecimalPlacesScale = bigUncertainty.scale() + final var twoDecimalPlacesScale = bigUncertainty.scale() - bigUncertainty.precision() + 2; - final BigDecimal roundedUncertainty = bigUncertainty + final var roundedUncertainty = bigUncertainty .setScale(twoDecimalPlacesScale, RoundingMode.HALF_EVEN); if (roundedUncertainty.unscaledValue().intValue() >= 20) return twoDecimalPlacesScale - 1; // one decimal place - else - return twoDecimalPlacesScale; + return twoDecimalPlacesScale; } @Override - public final int hashCode() { - final int prime = 31; - int result = 1; + public int hashCode() { + final var prime = 31; + var result = 1; result = prime * result + Double.hashCode(this.value); result = prime * result + Double.hashCode(this.uncertainty); return result; @@ -271,24 +263,24 @@ public final class UncertainDouble implements Comparable { /** * @return true iff the value has no uncertainty - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final boolean isExact() { + public boolean isExact() { return this.uncertainty == 0; } /** * Returns the difference of {@code this} and {@code other}. - * + * * @param other number to subtract * @return result of subtraction - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble minus(UncertainDouble other) { + public UncertainDouble minus(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.of(this.value - other.value, Math.hypot(this.uncertainty, other.uncertainty)); @@ -296,27 +288,27 @@ public final class UncertainDouble implements Comparable { /** * Returns the difference of {@code this} and the exact value {@code other}. - * + * * @param other number to subtract * @return result of subtraction - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble minusExact(double other) { + public UncertainDouble minusExact(double other) { return UncertainDouble.of(this.value - other, this.uncertainty); } /** * Returns the sum of {@code this} and {@code other}. - * + * * @param other number to add * @return result of addition - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble plus(UncertainDouble other) { + public UncertainDouble plus(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.of(this.value + other.value, Math.hypot(this.uncertainty, other.uncertainty)); @@ -324,14 +316,14 @@ public final class UncertainDouble implements Comparable { /** * Returns the sum of {@code this} and the exact value {@code other}. - * + * * @param other number to add * @return result of addition - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble plusExact(double other) { + public UncertainDouble plusExact(double other) { return UncertainDouble.of(this.value + other, this.uncertainty); } @@ -340,20 +332,20 @@ public final class UncertainDouble implements Comparable { * @since 2020-09-07 * @since v0.3.0 */ - public final double relativeUncertainty() { + public double relativeUncertainty() { return this.uncertainty / this.value; } /** * Returns the product of {@code this} and {@code other}. - * + * * @param other number to multiply * @return product - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble times(UncertainDouble other) { + public UncertainDouble times(UncertainDouble other) { Objects.requireNonNull(other, "other may not be null"); return UncertainDouble.ofRelative(this.value * other.value, Math .hypot(this.relativeUncertainty(), other.relativeUncertainty())); @@ -361,31 +353,31 @@ public final class UncertainDouble implements Comparable { /** * Returns the product of {@code this} and the exact value {@code other}. - * + * * @param other number to multiply * @return product - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble timesExact(double other) { + public 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}. - * + * * @param other exponent * @return result of exponentation - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble toExponent(UncertainDouble other) { + public 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( + final var result = Math.pow(this.value, other.value); + final var relativeUncertainty = Math.hypot( other.value * this.relativeUncertainty(), Math.log(this.value) * other.uncertainty); @@ -395,14 +387,14 @@ public final class UncertainDouble implements Comparable { /** * Returns the result of {@code this} raised the exact exponent * {@code other}. - * + * * @param other exponent * @return result of exponentation - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final UncertainDouble toExponentExact(double other) { + public UncertainDouble toExponentExact(double other) { return UncertainDouble.ofRelative(Math.pow(this.value, other), this.relativeUncertainty() * other); } @@ -413,21 +405,21 @@ public final class UncertainDouble implements Comparable { * This method returns the same value as * {@link #toString(boolean, RoundingMode)}, but {@code showUncertainty} is * true if and only if the uncertainty is non-zero. - * + * *

    * Examples: - * + * *

     	 * 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"
     	 * 
    - * + * * @since 2020-09-07 * @since v0.3.0 */ @Override - public final String toString() { + public String toString() { return this.toString(!this.isExact(), RoundingMode.HALF_EVEN); } @@ -447,7 +439,7 @@ public final class UncertainDouble implements Comparable { * digits otherwise it will be rounded to one significant digit. *

    * Examples: - * + * *

     	 * UncertainDouble.of(3.27, 0.22).toString(false) = "3.3"
     	 * UncertainDouble.of(3.27, 0.22).toString(true) = "3.3 ± 0.2"
    @@ -456,16 +448,15 @@ public final class UncertainDouble implements Comparable {
     	 * UncertainDouble.of(-5.01, 0).toString(false) = "-5.01"
     	 * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 ± 0.0"
     	 * 
    - * + * * @param showUncertainty uncertainty is only shown if this parameter is true - * @param roundingMode how to round values + * @param roundingMode how to round values * @return string representation of this {@code UncertainDouble} - * + * * @since 2020-09-07 * @since v0.3.0 */ - public final String toString(boolean showUncertainty, - RoundingMode roundingMode) { + public String toString(boolean showUncertainty, RoundingMode roundingMode) { String valueString, uncertaintyString; // generate the string representation of value and uncertainty @@ -475,13 +466,13 @@ public final class UncertainDouble implements Comparable { } else { // round the value and uncertainty according to getDisplayScale() - final BigDecimal bigValue = BigDecimal.valueOf(this.value); - final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty); + final var bigValue = BigDecimal.valueOf(this.value); + final var bigUncertainty = BigDecimal.valueOf(this.uncertainty); - final int displayScale = this.getDisplayScale(); - final BigDecimal roundedUncertainty = bigUncertainty + final var displayScale = this.getDisplayScale(); + final var roundedUncertainty = bigUncertainty .setScale(displayScale, roundingMode); - final BigDecimal roundedValue = bigValue.setScale(displayScale, + final var roundedValue = bigValue.setScale(displayScale, roundingMode); valueString = roundedValue.toString(); @@ -497,7 +488,7 @@ public final class UncertainDouble implements Comparable { * @since 2020-09-07 * @since v0.3.0 */ - public final double uncertainty() { + public double uncertainty() { return this.uncertainty; } @@ -506,7 +497,7 @@ public final class UncertainDouble implements Comparable { * @since 2020-09-07 * @since v0.3.0 */ - public final double value() { + public double value() { return this.value; } } diff --git a/src/main/java/sevenUnits/utils/package-info.java b/src/main/java/sevenUnits/utils/package-info.java index 6cae117..b600c17 100644 --- a/src/main/java/sevenUnits/utils/package-info.java +++ b/src/main/java/sevenUnits/utils/package-info.java @@ -17,7 +17,7 @@ /** * Supplementary classes that are not related to units, but are necessary for * their function. - * + * * @author Adrien Hopkins * @since 2019-03-14 * @since v0.2.0 diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java index 97df107..a441911 100644 --- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java +++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java @@ -56,7 +56,7 @@ public enum DefaultPrefixRepetitionRule implements Predicate> { final boolean magnifying; if (prefixes.isEmpty()) return true; - else if (prefixes.get(0).getMultiplier() > 1) { + if (prefixes.get(0).getMultiplier() > 1) { magnifying = true; } else { magnifying = false; @@ -68,15 +68,13 @@ public enum DefaultPrefixRepetitionRule implements Predicate> { if (!Metric.DECIMAL_PREFIXES.contains(prefixes.get(0))) return NO_REPETITION.test(prefixes); - int part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto, + var part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto, // 2=deka,hecto,deci,centi for (final UnitPrefix prefix : prefixes) { // check that the current prefix is metric and appropriately // magnifying/reducing - if (!Metric.DECIMAL_PREFIXES.contains(prefix)) - return false; - if (magnifying != prefix.getMultiplier() > 1) + if (!Metric.DECIMAL_PREFIXES.contains(prefix) || (magnifying != prefix.getMultiplier() > 1)) return false; // check if the current prefix is correct diff --git a/src/main/java/sevenUnitsGUI/DelegateListModel.java b/src/main/java/sevenUnitsGUI/DelegateListModel.java index 200eee2..da4f978 100644 --- a/src/main/java/sevenUnitsGUI/DelegateListModel.java +++ b/src/main/java/sevenUnitsGUI/DelegateListModel.java @@ -31,7 +31,7 @@ import javax.swing.AbstractListModel; * the delegated list's methods because the delegate methods handle updating the * list. *

    - * + * * @author Adrien Hopkins * @since 2019-01-14 * @since v0.1.0 @@ -46,7 +46,7 @@ final class DelegateListModel extends AbstractListModel /** * The list that this model is a delegate to. - * + * * @since 2019-01-14 * @since v0.1.0 */ @@ -54,7 +54,7 @@ final class DelegateListModel extends AbstractListModel /** * Creates an empty {@code DelegateListModel}. - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -64,7 +64,7 @@ final class DelegateListModel extends AbstractListModel /** * Creates the {@code DelegateListModel}. - * + * * @param delegate list to delegate * @since 2019-01-14 * @since v0.1.0 @@ -75,8 +75,8 @@ final class DelegateListModel extends AbstractListModel @Override public boolean add(final E element) { - final int index = this.delegate.size(); - final boolean success = this.delegate.add(element); + final var index = this.delegate.size(); + final var success = this.delegate.add(element); this.fireIntervalAdded(this, index, index); return success; } @@ -89,7 +89,7 @@ final class DelegateListModel extends AbstractListModel @Override public boolean addAll(final Collection c) { - boolean changed = false; + var changed = false; for (final E e : c) { if (this.add(e)) { changed = true; @@ -109,7 +109,7 @@ final class DelegateListModel extends AbstractListModel @Override public void clear() { - final int oldSize = this.delegate.size(); + final var oldSize = this.delegate.size(); this.delegate.clear(); if (oldSize >= 1) { this.fireIntervalRemoved(this, 0, oldSize - 1); @@ -177,7 +177,7 @@ final class DelegateListModel extends AbstractListModel @Override public E remove(final int index) { - final E returnValue = this.delegate.get(index); + final var returnValue = this.delegate.get(index); this.delegate.remove(index); this.fireIntervalRemoved(this, index, index); return returnValue; @@ -185,15 +185,15 @@ final class DelegateListModel extends AbstractListModel @Override public boolean remove(final Object o) { - final int index = this.delegate.indexOf(o); - final boolean returnValue = this.delegate.remove(o); + final var index = this.delegate.indexOf(o); + final var returnValue = this.delegate.remove(o); this.fireIntervalRemoved(this, index, index); return returnValue; } @Override public boolean removeAll(final Collection c) { - boolean changed = false; + var changed = false; for (final Object e : c) { if (this.remove(e)) { changed = true; @@ -204,15 +204,15 @@ final class DelegateListModel extends AbstractListModel @Override public boolean retainAll(final Collection c) { - final int oldSize = this.size(); - final boolean returnValue = this.delegate.retainAll(c); + final var oldSize = this.size(); + final var returnValue = this.delegate.retainAll(c); this.fireIntervalRemoved(this, this.size(), oldSize - 1); return returnValue; } @Override public E set(final int index, final E element) { - final E returnValue = this.delegate.get(index); + final var returnValue = this.delegate.get(index); this.delegate.set(index, element); this.fireContentsChanged(this, index, index); return returnValue; diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java index 20eb23c..ce69365 100644 --- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java +++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java @@ -18,7 +18,7 @@ package sevenUnitsGUI; /** * A View that can convert unit expressions - * + * * @author Adrien Hopkins * @since 2021-12-15 * @since v0.4.0 @@ -40,7 +40,7 @@ public interface ExpressionConversionView extends View { /** * Shows the output of an expression conversion to the user. - * + * * @param uc unit conversion to show * @since 2021-12-15 * @since v0.4.0 diff --git a/src/main/java/sevenUnitsGUI/FilterComparator.java b/src/main/java/sevenUnitsGUI/FilterComparator.java index d7a59c4..ff942fb 100644 --- a/src/main/java/sevenUnitsGUI/FilterComparator.java +++ b/src/main/java/sevenUnitsGUI/FilterComparator.java @@ -21,9 +21,9 @@ import java.util.Objects; /** * A comparator that compares strings using a filter. - * + * * @param type of element being compared - * + * * @author Adrien Hopkins * @since 2019-01-15 * @since v0.1.0 @@ -31,21 +31,21 @@ import java.util.Objects; final class FilterComparator implements Comparator { /** * The filter that the comparator is filtered by. - * + * * @since 2019-01-15 * @since v0.1.0 */ private final String filter; /** * The comparator to use if the arguments are otherwise equal. - * + * * @since 2019-01-15 * @since v0.1.0 */ private final Comparator comparator; /** * Whether or not the comparison is case-sensitive. - * + * * @since 2019-04-14 * @since v0.2.0 */ @@ -53,7 +53,7 @@ final class FilterComparator implements Comparator { /** * Creates the {@code FilterComparator}. - * + * * @param filter * @since 2019-01-15 * @since v0.1.0 @@ -64,7 +64,7 @@ final class FilterComparator implements Comparator { /** * Creates the {@code FilterComparator}. - * + * * @param filter string to filter by * @param comparator comparator to fall back to if all else fails, null is * compareTo. @@ -79,7 +79,7 @@ final class FilterComparator implements Comparator { /** * Creates the {@code FilterComparator}. - * + * * @param filter string to filter by * @param comparator comparator to fall back to if all else fails, null is * compareTo. @@ -118,19 +118,18 @@ final class FilterComparator implements Comparator { // 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)) + 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)) + if (!str0.contains(this.filter) && !str1.contains(this.filter)) return 1; // other elements go last if (this.comparator == null) return str0.compareTo(str1); - else - return this.comparator.compare(arg0, arg1); + return this.comparator.compare(arg0, arg1); } } diff --git a/src/main/java/sevenUnitsGUI/GridBagBuilder.java b/src/main/java/sevenUnitsGUI/GridBagBuilder.java index 81d1e79..a9fede3 100644 --- a/src/main/java/sevenUnitsGUI/GridBagBuilder.java +++ b/src/main/java/sevenUnitsGUI/GridBagBuilder.java @@ -21,7 +21,7 @@ import java.awt.Insets; /** * A builder for Java's {@link java.awt.GridBagConstraints} class. - * + * * @author Adrien Hopkins * @since 2018-11-30 * @since v0.1.0 @@ -40,7 +40,7 @@ final class GridBagBuilder { *

    * The default value is RELATIVE. gridx should be a * non-negative value. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#gridy @@ -58,7 +58,7 @@ final class GridBagBuilder { *

    * The default value is RELATIVE. gridy should be a * non-negative value. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#gridx @@ -76,7 +76,7 @@ final class GridBagBuilder { * from gridx to the next to the last one in its row. *

    * gridwidth should be non-negative and the default value is 1. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#gridheight @@ -96,7 +96,7 @@ final class GridBagBuilder { *

    * gridheight should be a non-negative value and the default * value is 1. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#gridwidth @@ -119,7 +119,7 @@ final class GridBagBuilder { *

    * The default value of this field is 0. weightx * should be a non-negative value. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#weighty @@ -142,7 +142,7 @@ final class GridBagBuilder { *

    * The default value of this field is 0. weighty * should be a non-negative value. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#weightx @@ -173,7 +173,7 @@ final class GridBagBuilder { * BELOW_BASELINE, BELOW_BASELINE_LEADING, and * BELOW_BASELINE_TRAILING. The default value is * CENTER. - * + * * @serial * @see #clone() * @see java.awt.ComponentOrientation @@ -199,7 +199,7 @@ final class GridBagBuilder { * *

    * The default value is NONE. - * + * * @serial * @see #clone() */ @@ -212,7 +212,7 @@ final class GridBagBuilder { * amount of space between the component and the edges of its display area. *

    * The default value is new Insets(0, 0, 0, 0). - * + * * @serial * @see #clone() */ @@ -226,7 +226,7 @@ final class GridBagBuilder { * is at least its minimum width plus ipadx pixels. *

    * The default value is 0. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#ipady @@ -241,7 +241,7 @@ final class GridBagBuilder { * least its minimum height plus ipady pixels. *

    * The default value is 0. - * + * * @serial * @see #clone() * @see java.awt.GridBagConstraints#ipadx @@ -292,7 +292,6 @@ final class GridBagBuilder { 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; this.gridwidth = gridwidth; diff --git a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java index 2ea0923..73d12bc 100644 --- a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java +++ b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java @@ -73,7 +73,7 @@ public final class PrefixSearchRule implements * @since 2022-07-06 * @since v0.4.0 */ - public static final PrefixSearchRule getCoherentOnlyRule( + public static PrefixSearchRule getCoherentOnlyRule( Set prefixes) { return new PrefixSearchRule(prefixes, u -> u.isCoherent() && !u.getName().equals("kilogram")); @@ -87,19 +87,14 @@ public final class PrefixSearchRule implements * @since 2022-07-06 * @since v0.4.0 */ - public static final PrefixSearchRule getUniversalRule( - Set prefixes) { + public static PrefixSearchRule getUniversalRule(Set prefixes) { return new PrefixSearchRule(prefixes, u -> true); } - /** - * The set of prefixes that will be applied to the unit. - */ + /** The set of prefixes that will be applied to the unit. */ private final Set prefixes; - /** - * Determines which units are given prefixes. - */ + /** Determines which units are given prefixes. */ private final Predicate prefixableUnitRule; /** @@ -118,8 +113,8 @@ public final class PrefixSearchRule implements @Override public Map apply(Entry t) { final Map outputUnits = new HashMap<>(); - final String originalName = t.getKey(); - final LinearUnit originalUnit = t.getValue(); + final var originalName = t.getKey(); + final var originalUnit = t.getValue(); outputUnits.put(originalName, originalUnit); if (this.prefixableUnitRule.test(originalUnit)) { for (final UnitPrefix prefix : this.prefixes) { @@ -136,7 +131,7 @@ public final class PrefixSearchRule implements return true; if (!(obj instanceof PrefixSearchRule)) return false; - final PrefixSearchRule other = (PrefixSearchRule) obj; + final var other = (PrefixSearchRule) obj; return Objects.equals(this.prefixableUnitRule, other.prefixableUnitRule) && Objects.equals(this.prefixes, other.prefixes); } diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 9913e89..7fd979a 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -86,11 +86,11 @@ public final class Presenter { * */ static final String DEFAULT_LOCALE = "en"; - + private static final List LOCAL_LOCALES = List.of("en", "fr"); private static final Path USER_LOCALES_DIR = userConfigDir() .resolve("SevenUnits").resolve("locales"); - + /** * Adds default units and dimensions to a database. * @@ -112,14 +112,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); } - + private static String displayRuleToString( Function numberDisplayRule) { if (numberDisplayRule instanceof FixedDecimals) @@ -128,12 +128,11 @@ public final class Presenter { if (numberDisplayRule instanceof FixedPrecision) return String.format("FIXED_PRECISION %d", ((FixedPrecision) numberDisplayRule).significantFigures()); - else if (numberDisplayRule instanceof UncertaintyBased) + if (numberDisplayRule instanceof UncertaintyBased) return "UNCERTAINTY_BASED"; - else - return numberDisplayRule.toString(); + return numberDisplayRule.toString(); } - + /** * Determines where to wrap {@code toWrap} with a max line length of * {@code maxLineLength}. If no good spot is found, returns -1. @@ -148,7 +147,7 @@ public final class Presenter { } return -1; } - + /** * Gets the text of a resource file as a set of strings (each one is one line * of the text). @@ -160,7 +159,7 @@ public final class Presenter { */ private static List getLinesFromResource(String filename) { final List lines = new ArrayList<>(); - + try (var stream = inputStream(filename); var scanner = new Scanner(stream)) { while (scanner.hasNextLine()) { @@ -170,10 +169,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. * @@ -185,7 +184,7 @@ public final class Presenter { private static InputStream inputStream(String filepath) { return Presenter.class.getResourceAsStream(filepath); } - + /** * Convert a linear unit value to a string, where the number is rounded to * the nearest integer. @@ -196,38 +195,37 @@ public final class Presenter { private static String linearUnitValueIntToString(LinearUnitValue uv) { return Long.toString(Math.round(uv.getValueExact())) + " " + uv.getUnit(); } - + private static Map.Entry parseSettingLine(String line) { final var equalsIndex = line.indexOf('='); if (equalsIndex == -1) throw new IllegalStateException( "Settings file is malformed at line: " + line); - + final var param = line.substring(0, equalsIndex); final var value = line.substring(equalsIndex + 1); - + return Map.entry(param, value); } - + /** Gets a Path from a pathname in the config file. */ private static Path pathFromConfig(String pathname) { return CONFIG_FILE.getParent().resolve(pathname); } - + // ====== SETTINGS ====== - + private static String searchRuleToString( Function, Map> searchRule) { if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) return "NO_PREFIXES"; if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) return "COMMON_PREFIXES"; - else if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule)) + if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule)) return "ALL_METRIC_PREFIXES"; - else - return searchRule.toString(); + return searchRule.toString(); } - + /** * @return true iff a and b have any elements in common * @since 2022-04-19 @@ -240,22 +238,20 @@ public final class Presenter { } return false; } - + private static Path userConfigDir() { if (System.getProperty("os.name").startsWith("Windows")) { final var envFolder = System.getenv("LOCALAPPDATA"); if (envFolder == null || "".equals(envFolder)) return Path.of(System.getenv("USERPROFILE"), "AppData", "Local"); - else - return Path.of(envFolder); + return Path.of(envFolder); } final var envFolder = System.getenv("XDG_CONFIG_HOME"); if (envFolder == null || "".equals(envFolder)) return Path.of(System.getenv("HOME"), ".config"); - else - return Path.of(envFolder); + return Path.of(envFolder); } - + /** * @return {@code line} with any comments removed. * @since 2021-03-13 @@ -265,7 +261,7 @@ public final class Presenter { final var index = line.indexOf('#'); return index == -1 ? line : line.substring(0, index); } - + /** * Wraps a string, ensuring no line is longer than {@code maxLineLength}. * @@ -290,23 +286,21 @@ public final class Presenter { wrapped.append(remaining); return wrapped.toString(); } - - /** - * The view that this presenter communicates with - */ + + /** 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. Not implemented yet. */ private Function 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 @@ -314,60 +308,60 @@ public final class Presenter { */ private Function 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> 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> 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 metricExceptions; - + /** maps locale names (e.g. 'en') to key-text maps */ final Map> locales; - + /** name of locale in locales to use */ String userLocale; - + /** * 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; - + /** * The default unit, prefix, dimension and exception data will only be loaded * if this variable is true. */ private boolean useDefaultDatafiles = true; - + /** Custom unit datafiles that will be loaded by {@link #reloadData} */ private final Set customUnitFiles = new HashSet<>(); /** Custom dimension datafiles that will be loaded by {@link #reloadData} */ private final Set customDimensionFiles = new HashSet<>(); /** Custom exception datafiles that will be loaded by {@link #reloadData} */ private final Set customExceptionFiles = new HashSet<>(); - + /** * Creates a Presenter * @@ -379,40 +373,40 @@ public final class Presenter { this.view = view; this.database = new UnitDatabase(); this.metricExceptions = new HashSet<>(); - + this.locales = this.loadLocales(); this.userLocale = DEFAULT_LOCALE; - + // set default settings temporarily if (Files.exists(CONFIG_FILE)) { this.loadSettings(CONFIG_FILE); } - + this.reloadData(); - + // print out unit counts System.out.println(this.loadStatMsg()); } - + private void addLocaleFile(Map> locales, Path file) throws IOException { final Map locale = new HashMap<>(); - final String fileName = file.getName(file.getNameCount() - 1).toString(); - final String localeName = fileName.substring(0, fileName.length() - 4); - try (Stream lines = Files.lines(file)) { + final var fileName = file.getName(file.getNameCount() - 1).toString(); + final var localeName = fileName.substring(0, fileName.length() - 4); + try (var lines = Files.lines(file)) { lines.forEach(line -> this.addLocaleLine(locale, line)); } locales.put(localeName, locale); } - + private void addLocaleLine(Map locale, String line) { - final String[] parts = line.split("=", 2); + final var parts = line.split("=", 2); if (parts.length < 2) return; - + locale.put(parts[0], parts[1]); } - + /** * Applies a search rule to an entry in a name-unit map. * @@ -433,7 +427,7 @@ public final class Presenter { } 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. @@ -450,10 +444,10 @@ public final class Presenter { throw new UnsupportedOperationException( "This function can only be called when the view is an ExpressionConversionView"); final var xcview = (ExpressionConversionView) this.view; - + final var fromExpression = xcview.getFromExpression(); final var toExpression = xcview.getToExpression(); - + // expressions must not be empty if (fromExpression.isEmpty()) { this.view.showErrorMessage("Parse Error", @@ -465,7 +459,7 @@ public final class Presenter { "Please enter a unit expression in the To: box."); return; } - + final Optional uc; if (this.database.containsUnitSetName(toExpression)) { uc = this.convertExpressionToNamedMultiUnit(fromExpression, @@ -476,10 +470,10 @@ public final class Presenter { } else { uc = this.convertExpressionToExpression(fromExpression, toExpression); } - + uc.ifPresent(xcview::showExpressionConversionOutput); } - + /** * Converts a unit expression to another expression. * @@ -508,7 +502,7 @@ public final class Presenter { "Could not recognize text in To entry: " + e.getMessage()); return Optional.empty(); } - + // convert and show output if (!from.getUnit().canConvertTo(to)) { this.view.showErrorMessage("Conversion Error", @@ -517,7 +511,7 @@ public final class Presenter { return Optional.empty(); } final UncertainDouble uncertainValue; - + // uncertainty is meaningless for non-linear units, so we will have // to erase uncertainty information for them if (to instanceof LinearUnit) { @@ -527,13 +521,13 @@ public final class Presenter { final var value = from.asUnitValue().convertTo(to).getValue(); uncertainValue = UncertainDouble.of(value, 0); } - + final var uc = UnitConversionRecord.valueOf(fromExpression, toExpression, "", this.numberDisplayRule.apply(uncertainValue)); return Optional.of(uc); - + } - + /** * Convert an expression to a MultiUnit. If an error happened, it is shown to * the view and Optional.empty() is returned. @@ -551,7 +545,7 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - + final List toUnits = new ArrayList<>(toExpressions.length); for (final String toExpression : toExpressions) { try { @@ -570,7 +564,7 @@ public final class Presenter { return Optional.empty(); } } - + final List toValues; try { toValues = from.convertToMultiple(toUnits); @@ -579,12 +573,12 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Convert an expression to a MultiUnit with a name from the database. If an * error happened, it is shown to the view and Optional.empty() is returned. @@ -602,7 +596,7 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - + final var toUnits = this.database.getUnitSet(toName); final List toValues; try { @@ -612,12 +606,12 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Converts from the view's input unit to its output unit. Displays an error * message if any of the required fields are invalid. @@ -634,36 +628,33 @@ public final class Presenter { throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); final var ucview = (UnitConversionView) this.view; - + final var fromUnitOptional = ucview.getFromSelection(); final var toUnitOptional = ucview.getToSelection(); final var inputValueString = ucview.getInputValue(); - + // extract values from optionals final String fromUnitString, toUnitString; - if (fromUnitOptional.isPresent()) { - fromUnitString = fromUnitOptional.orElseThrow(); - } else { + if (!fromUnitOptional.isPresent()) { this.view.showErrorMessage("Unit Selection Error", "Please specify a From unit"); return; } - if (toUnitOptional.isPresent()) { - toUnitString = toUnitOptional.orElseThrow(); - } else { + fromUnitString = fromUnitOptional.orElseThrow(); + if (!toUnitOptional.isPresent()) { this.view.showErrorMessage("Unit Selection Error", "Please specify a To unit"); return; } - + toUnitString = toUnitOptional.orElseThrow(); + // convert strings to data, checking if anything is invalid final Unit fromUnit; final UncertainDouble uncertainValue; - - if (this.database.containsUnitName(fromUnitString)) { - fromUnit = this.database.getUnit(fromUnitString); - } else + + if (!this.database.containsUnitName(fromUnitString)) throw this.viewError("Nonexistent From unit: %s", fromUnitString); + fromUnit = this.database.getUnit(fromUnitString); try { uncertainValue = UncertainDouble.fromRoundedString(inputValueString); } catch (final NumberFormatException e) { @@ -683,7 +674,7 @@ public final class Presenter { } else throw this.viewError("Nonexistent To unit: %s", toUnitString); } - + private UnitConversionRecord convertUnitToMulti(String fromUnitString, String inputValueString, Unit fromUnit, List toMulti, UncertainDouble uncertainValue) { @@ -692,7 +683,7 @@ public final class Presenter { throw this.viewError("Could not convert between %s and %s", fromUnit, toUnit); } - + final LinearUnitValue initValue; if (fromUnit instanceof LinearUnit) { final var fromLinear = (LinearUnit) fromUnit; @@ -701,21 +692,21 @@ public final class Presenter { initValue = UnitValue.of(fromUnit, uncertainValue.value()) .convertToBase(NameSymbol.EMPTY); } - + final var converted = initValue.convertToMultiple(toMulti); final var toExpression = this.linearUnitValueSumToString(converted); return UnitConversionRecord.valueOf(fromUnitString, toExpression, inputValueString, ""); - + } - + private UnitConversionRecord convertUnitToUnit(String fromUnitString, String toUnitString, String inputValueString, Unit fromUnit, Unit toUnit, UncertainDouble uncertainValue) { 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; @@ -725,21 +716,21 @@ public final class Presenter { final var initialValue = LinearUnitValue.of(fromLinear, uncertainValue); final var converted = initialValue.convertTo(toLinear); - + outputValueString = this.numberDisplayRule.apply(converted.getValue()); } else { final var initialValue = UnitValue.of(fromUnit, uncertainValue.value()); final var converted = initialValue.convertTo(toUnit); - + outputValueString = this.numberDisplayRule .apply(UncertainDouble.of(converted.getValue(), 0)); } - + return UnitConversionRecord.valueOf(fromUnitString, toUnitString, inputValueString, outputValueString); } - + /** * @return true iff duplicate units are shown in unit lists * @since 2022-03-30 @@ -748,44 +739,44 @@ public final class Presenter { public boolean duplicatesShown() { return this.showDuplicates; } - + private String formatAboutText(Stream rawLines) { return rawLines.map(Presenter::withoutComments) .collect(Collectors.joining("\n")) .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); } - + /** * @return text in About file * @since 2022-02-19 * @since v0.4.0 */ public String getAboutText() { - final Path customFilepath = Presenter + final var customFilepath = Presenter .pathFromConfig("about/" + this.userLocale + ".txt"); if (Files.exists(customFilepath)) { - try (Stream lines = Files.lines(customFilepath)) { + try (var lines = Files.lines(customFilepath)) { return this.formatAboutText(lines); } catch (final IOException e) { - final String filename = String.format("/about/%s.txt", + final var filename = String.format("/about/%s.txt", this.userLocale); return this.formatAboutText( Presenter.getLinesFromResource(filename).stream()); } - } else if (LOCAL_LOCALES.contains(this.userLocale)) { - final String filename = String.format("/about/%s.txt", + } + if (LOCAL_LOCALES.contains(this.userLocale)) { + final var filename = String.format("/about/%s.txt", this.userLocale); return this.formatAboutText( Presenter.getLinesFromResource(filename).stream()); - } else { - final String filename = String.format("/about/%s.txt", DEFAULT_LOCALE); - return this.formatAboutText( - Presenter.getLinesFromResource(filename).stream()); } - + final var filename = String.format("/about/%s.txt", DEFAULT_LOCALE); + return this + .formatAboutText(Presenter.getLinesFromResource(filename).stream()); + } - + /** * @return set of all locales available to select * @since 2025-02-21 @@ -794,7 +785,7 @@ public final class Presenter { public Set getAvailableLocales() { return this.locales.keySet(); } - + /** * Gets a name for this dimension using the database * @@ -810,7 +801,7 @@ public final class Presenter { .filter(d -> d.equals(dimension)).findAny().map(Nameable::getName) .orElse(dimension.toString(Nameable::getName)); } - + /** * Gets the correct text for a provided ID. If this text is available in the * user's locale, that text is provided. Otherwise, text is taken from the @@ -824,7 +815,7 @@ public final class Presenter { final var defaultLocale = this.locales.get(DEFAULT_LOCALE); return userLocale.getOrDefault(textID, defaultLocale.get(textID)); } - + /** * @return the rule that is used by this presenter to convert numbers into * strings @@ -834,7 +825,7 @@ public final class Presenter { public Function getNumberDisplayRule() { return this.numberDisplayRule; } - + /** * @return the rule that is used by this presenter to convert strings into * numbers @@ -845,7 +836,7 @@ public final class Presenter { private Function getNumberParsingRule() { return this.numberParsingRule; } - + /** * @return the rule that determines whether a set of prefixes is valid * @since 2022-04-19 @@ -854,7 +845,7 @@ public final class Presenter { public Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * @return the rule that determines which units are prefixed * @since 2022-07-08 @@ -863,7 +854,7 @@ public final class Presenter { public Function, Map> getSearchRule() { return this.searchRule; } - + /** * @return a search rule that shows all single prefixes * @since 2022-07-08 @@ -873,7 +864,7 @@ public final class Presenter { return PrefixSearchRule.getCoherentOnlyRule( new HashSet<>(this.database.prefixMap(true).values())); } - + /** * @return user's selected locale * @since 2025-02-21 @@ -882,7 +873,7 @@ public final class Presenter { public String getUserLocale() { return this.userLocale; } - + /** * @return the view associated with this presenter * @since 2022-04-19 @@ -891,7 +882,7 @@ public final class Presenter { public View getView() { return this.view; } - + /** * Accepts a list of errors. If that list is non-empty, prints an error * message and alerts the user. @@ -910,7 +901,7 @@ public final class Presenter { errorMessage); } } - + /** * @return whether or not the provided unit is semi-metric (i.e. an * exception) @@ -927,7 +918,7 @@ public final class Presenter { && this.metricExceptions.contains(symbol.orElseThrow()) || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } - + /** * Convert a list of LinearUnitValues that you would get from a unit-set * conversion to a string. All but the last have their numbers rendered as @@ -945,10 +936,8 @@ public final class Presenter { return integerPart + " + " + this.numberDisplayRule.apply(last.getValue()) + " " + last.getUnit(); } - - /** - * Load units, prefixes and dimensions from the default files. - */ + + /** Load units, prefixes and dimensions from the default files. */ private void loadDefaultData() { // load units and prefixes try (final var units = inputStream(DEFAULT_UNITS_FILEPATH)) { @@ -956,7 +945,7 @@ public final class Presenter { } catch (final IOException e) { throw new AssertionError("Loading of unitsfile.txt failed.", e); } - + // load dimensions try (final var dimensions = inputStream(DEFAULT_DIMENSIONS_FILEPATH)) { this.handleLoadErrors( @@ -964,7 +953,7 @@ public final class Presenter { } catch (final IOException e) { throw new AssertionError("Loading of dimensionfile.txt failed.", e); } - + // load metric exceptions try { try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); @@ -981,7 +970,7 @@ public final class Presenter { e); } } - + private void loadExceptionFile(Path exceptionFile) { try (var lines = Files.lines(exceptionFile)) { lines.map(Presenter::withoutComments) @@ -992,7 +981,7 @@ public final class Presenter { + exceptionFile + "\": " + e.getLocalizedMessage()); } } - + /** * Loads all available locales, including custom ones, into a map. * @@ -1002,14 +991,14 @@ public final class Presenter { final Map> locales = new HashMap<>(); for (final String localeName : LOCAL_LOCALES) { final Map locale = new HashMap<>(); - final String filename = "/locales/" + localeName + ".txt"; + final var filename = "/locales/" + localeName + ".txt"; getLinesFromResource(filename) .forEach(line -> this.addLocaleLine(locale, line)); locales.put(localeName, locale); } - + if (Files.exists(USER_LOCALES_DIR)) { - try (Stream files = Files.list(USER_LOCALES_DIR)) { + try (var files = Files.list(USER_LOCALES_DIR)) { files.forEach(localeFile -> { try { this.addLocaleFile(locales, localeFile); @@ -1023,7 +1012,7 @@ public final class Presenter { } return locales; } - + /** * Loads settings from the user's settings file and applies them to the * presenter. @@ -1036,11 +1025,11 @@ public final class Presenter { this.customDimensionFiles.clear(); this.customExceptionFiles.clear(); this.customUnitFiles.clear(); - + for (final Map.Entry setting : this .settingsFromFile(settingsFile)) { final var value = setting.getValue(); - + switch (setting.getKey()) { // set manually to avoid the unnecessary saving of the non-manual // methods @@ -1090,12 +1079,12 @@ public final class Presenter { break; } } - + if (this.view.getPresenter() != null) { this.updateView(); } } - + /** * @return a message showing how much stuff has been loaded * @since 2024-08-22 @@ -1120,19 +1109,19 @@ public final class Presenter { .replace("[d]", Integer.toString(this.database.dimensionMap().size())); } - + /** * @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 * unit list and imperial/USC units removed from the To unit list) - * + * * @since 2022-03-30 * @since v0.4.0 */ 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 @@ -1147,11 +1136,11 @@ public final class Presenter { final var ucview = (UnitConversionView) this.view; ucview.setDimensionNames(this.database.dimensionMap().keySet()); } - + this.updateView(); this.view.updateText(); } - + void prefixSelected() { final var selectedPrefixName = this.view.getViewedPrefixName(); final Optional selectedPrefix = selectedPrefixName @@ -1162,26 +1151,24 @@ public final class Presenter { .ifPresent(prefix -> this.view.showPrefix(prefix.getNameSymbol(), String.valueOf(prefix.getMultiplier()))); } - - /** - * Clears then reloads all unit, prefix, dimension and exception data. - */ + + /** Clears then reloads all unit, prefix, dimension and exception data. */ public void reloadData() { this.database.clear(); this.metricExceptions.clear(); addDefaults(this.database); - + if (this.useDefaultDatafiles) { this.loadDefaultData(); } - + this.customUnitFiles.forEach( path -> this.handleLoadErrors(this.database.loadUnitsFile(path))); this.customDimensionFiles.forEach(path -> this .handleLoadErrors(this.database.loadDimensionFile(path))); this.customExceptionFiles.forEach(this::loadExceptionFile); } - + /** * Saves the presenter's current settings to the config file, creating it if * it doesn't exist. @@ -1199,10 +1186,10 @@ public final class Presenter { return false; } } - + return this.writeSettings(CONFIG_FILE); } - + private void setDisplayRuleFromString(String ruleString) { final var tokens = ruleString.split(" "); switch (tokens[0]) { @@ -1223,7 +1210,7 @@ public final class Presenter { break; } } - + /** * @param numberDisplayRule the new rule that will be used by this presenter * to convert numbers into strings @@ -1234,7 +1221,7 @@ public final class Presenter { Function numberDisplayRule) { this.numberDisplayRule = numberDisplayRule; } - + /** * @param numberParsingRule the new rule that will be used by this presenter * to convert strings into numbers @@ -1246,7 +1233,7 @@ public final class Presenter { Function numberParsingRule) { this.numberParsingRule = numberParsingRule; } - + /** * @param oneWayConversionEnabled whether not one-way conversion should be * enabled @@ -1258,7 +1245,7 @@ public final class Presenter { this.oneWayConversionEnabled = oneWayConversionEnabled; this.updateView(); } - + /** * @param prefixRepetitionRule the rule that determines whether a set of * prefixes is valid @@ -1270,7 +1257,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 @@ -1283,7 +1270,7 @@ public final class Presenter { Function, Map> searchRule) { this.searchRule = searchRule; } - + private void setSearchRuleFromString(String ruleString) { switch (ruleString) { case "NO_PREFIXES": @@ -1301,7 +1288,7 @@ public final class Presenter { ruleString); } } - + /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 @@ -1311,7 +1298,7 @@ public final class Presenter { this.showDuplicates = showDuplicateUnits; this.updateView(); } - + private List> settingsFromFile(Path settingsFile) { try (var lines = Files.lines(settingsFile)) { return lines.map(Presenter::withoutComments) @@ -1323,11 +1310,11 @@ public final class Presenter { return null; } } - + /** * Sets whether or not the default datafiles will be loaded. This method * automatically updates the view's units. - * + * * @param useDefaultDatafiles whether or not default datafiles should be * loaded */ @@ -1336,7 +1323,7 @@ public final class Presenter { this.reloadData(); this.updateView(); } - + /** * Sets the user's locale, updating the view. * @@ -1346,7 +1333,7 @@ public final class Presenter { this.userLocale = userLocale; this.view.updateText(); } - + /** * Shows a unit in the unit viewer * @@ -1363,7 +1350,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. @@ -1380,7 +1367,7 @@ public final class Presenter { : null); selectedUnit.ifPresent(this::showUnit); } - + /** * Updates the view's From and To units, if it has some * @@ -1391,20 +1378,20 @@ public final class Presenter { if (this.view instanceof UnitConversionView) { final var 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(); var unitSets = this.database.unitSetMap().entrySet().stream(); - + // filter by dimension, if one is selected if (selectedDimensionName.isPresent()) { final var viewDimension = this.database @@ -1416,7 +1403,7 @@ public final class Presenter { unitSets = unitSets.filter(us -> viewDimension .equals(us.getValue().get(0).getDimension())); } - + // filter by unit type, if desired if (this.oneWayConversionEnabled) { fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(), @@ -1427,7 +1414,7 @@ public final class Presenter { unitSets = unitSets .filter(us -> this.metricExceptions.contains(us.getKey())); } - + // set unit names ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule) .map(Map.Entry::getKey).collect(Collectors.toSet())); @@ -1439,14 +1426,12 @@ public final class Presenter { ucview.setToUnitNames(toNames); } } - - /** - * @return true iff the default datafiles are being used - */ + + /** @return true iff the default datafiles are being used */ public boolean usingDefaultDatafiles() { return this.useDefaultDatafiles; } - + /** * @param message message to add * @param args string formatting arguments for message @@ -1459,7 +1444,7 @@ public final class Presenter { return new AssertionError("View Programming Error (from " + this.view + "): " + String.format(message, args)); } - + /** * Saves the presenter's settings to the user settings file. * diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java index 43a57ce..bddce04 100644 --- a/src/main/java/sevenUnitsGUI/SearchBoxList.java +++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java @@ -49,7 +49,7 @@ final class SearchBoxList extends JPanel { /** * The text to place in an empty search box. - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -57,7 +57,7 @@ final class SearchBoxList extends JPanel { /** * The color to use for an empty foreground. - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -82,7 +82,7 @@ final class SearchBoxList extends JPanel { /** * Creates an empty SearchBoxList - * + * * @since 2022-02-19 * @since v0.4.0 */ @@ -92,7 +92,7 @@ final class SearchBoxList extends JPanel { /** * Creates the {@code SearchBoxList}. - * + * * @param itemsToFilter items to put in the list * @since 2019-04-14 * @since v0.2.0 @@ -103,12 +103,12 @@ final class SearchBoxList extends JPanel { /** * Creates the {@code SearchBoxList}. - * + * * @param itemsToFilter items to put in the list * @param defaultOrdering default ordering of items after filtration * (null=Comparable) * @param caseSensitive whether or not the filtration is case-sensitive - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -149,7 +149,7 @@ final class SearchBoxList extends JPanel { /** * Adds an additional filter for searching. - * + * * @param filter filter to add. * @since 2019-04-13 * @since v0.2.0 @@ -160,7 +160,7 @@ final class SearchBoxList extends JPanel { /** * Resets the search filter. - * + * * @since 2019-04-13 * @since v0.2.0 */ @@ -183,7 +183,7 @@ final class SearchBoxList extends JPanel { * @since 2019-04-14 * @since v0.2.0 */ - public final JTextField getSearchBox() { + public JTextField getSearchBox() { return this.searchBox; } @@ -197,9 +197,8 @@ final class SearchBoxList extends JPanel { private Predicate getSearchFilter(final String searchText) { if (this.caseSensitive) return item -> item.toString().contains(searchText); - else - return item -> item.toString().toLowerCase() - .contains(searchText.toLowerCase()); + return item -> item.toString().toLowerCase() + .contains(searchText.toLowerCase()); } /** @@ -207,7 +206,7 @@ final class SearchBoxList extends JPanel { * @since 2019-04-14 * @since v0.2.0 */ - public final JList getSearchList() { + public JList getSearchList() { return this.searchItems; } @@ -231,16 +230,16 @@ final class SearchBoxList extends JPanel { /** * Re-applies the filters. - * + * * @since 2019-04-13 * @since v0.2.0 */ public void reapplyFilter() { - final String searchText = this.searchBoxEmpty ? "" + final var searchText = this.searchBoxEmpty ? "" : this.searchBox.getText(); - final FilterComparator comparator = new FilterComparator<>(searchText, + final var comparator = new FilterComparator(searchText, this.defaultOrdering, this.caseSensitive); - final Predicate searchFilter = this.getSearchFilter(searchText); + final var searchFilter = this.getSearchFilter(searchText); this.listModel.clear(); this.itemsToFilter.forEach(item -> { @@ -258,7 +257,7 @@ final class SearchBoxList extends JPanel { /** * Runs whenever the search box gains focus. - * + * * @param e focus event * @since 2019-04-13 * @since v0.2.0 @@ -273,7 +272,7 @@ final class SearchBoxList extends JPanel { /** * Runs whenever the search box loses focus. - * + * * @param e focus event * @since 2019-04-13 * @since v0.2.0 @@ -291,7 +290,7 @@ final class SearchBoxList extends JPanel { *

    * Reapplies the search filter, and custom filters. *

    - * + * * @since 2019-04-14 * @since v0.2.0 */ @@ -299,11 +298,11 @@ final class SearchBoxList extends JPanel { if (this.searchBoxFocused) { this.searchBoxEmpty = this.searchBox.getText().equals(""); } - final String searchText = this.searchBoxEmpty ? "" + final var searchText = this.searchBoxEmpty ? "" : this.searchBox.getText(); - final FilterComparator comparator = new FilterComparator<>(searchText, + final var comparator = new FilterComparator(searchText, this.defaultOrdering, this.caseSensitive); - final Predicate searchFilter = this.getSearchFilter(searchText); + final var searchFilter = this.getSearchFilter(searchText); // initialize list with items that match the filter then sort this.listModel.clear(); @@ -336,7 +335,7 @@ final class SearchBoxList extends JPanel { /** * Manually updates the search box's item list. - * + * * @since 2020-08-27 * @since v0.3.0 */ diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java index d710117..16d31ae 100644 --- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java +++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java @@ -43,9 +43,7 @@ public final class StandardDisplayRules { /** Regular expression used for converting this to a string. */ public static final Pattern TO_STRING_PATTERN = Pattern .compile("Round to (\\d+) decimal places"); - /** - * The number of places to round to. - */ + /** The number of places to round to. */ private final int decimalPlaces; /** @@ -79,7 +77,7 @@ public final class StandardDisplayRules { return true; if (!(obj instanceof FixedDecimals)) return false; - final FixedDecimals other = (FixedDecimals) obj; + final var other = (FixedDecimals) obj; if (this.decimalPlaces != other.decimalPlaces) return false; return true; @@ -108,9 +106,7 @@ public final class StandardDisplayRules { public static final Pattern TO_STRING_PATTERN = Pattern .compile("Round to (\\d+) significant figures"); - /** - * The number of significant figures to round to. - */ + /** The number of significant figures to round to. */ private final MathContext mathContext; /** @@ -135,7 +131,7 @@ public final class StandardDisplayRules { return true; if (!(obj instanceof FixedPrecision)) return false; - final FixedPrecision other = (FixedPrecision) obj; + final var other = (FixedPrecision) obj; if (this.mathContext == null) { if (other.mathContext != null) return false; @@ -202,7 +198,7 @@ public final class StandardDisplayRules { * @since 2022-04-18 * @since v0.4.0 */ - public static final FixedDecimals fixedDecimals(int decimalPlaces) { + public static FixedDecimals fixedDecimals(int decimalPlaces) { return new FixedDecimals(decimalPlaces); } @@ -213,7 +209,7 @@ public final class StandardDisplayRules { * @since 2022-04-18 * @since v0.4.0 */ - public static final FixedPrecision fixedPrecision(int significantFigures) { + public static FixedPrecision fixedPrecision(int significantFigures) { return new FixedPrecision(significantFigures); } @@ -227,7 +223,7 @@ public final class StandardDisplayRules { * @since 2021-12-24 * @since v0.4.0 */ - public static final Function getStandardRule( + public static Function getStandardRule( String ruleToString) { if (UNCERTAINTY_BASED_ROUNDING_RULE.toString().equals(ruleToString)) return UNCERTAINTY_BASED_ROUNDING_RULE; @@ -236,13 +232,13 @@ public final class StandardDisplayRules { final var placesMatch = FixedDecimals.TO_STRING_PATTERN .matcher(ruleToString); if (placesMatch.matches()) - return new FixedDecimals(Integer.valueOf(placesMatch.group(1))); + return new FixedDecimals(Integer.parseInt(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))); + return new FixedPrecision(Integer.parseInt(sigFigMatch.group(1))); throw new IllegalArgumentException( "Provided string does not match any given rules."); @@ -253,7 +249,7 @@ public final class StandardDisplayRules { * @since 2022-04-18 * @since v0.4.0 */ - public static final UncertaintyBased uncertaintyBased() { + public static UncertaintyBased uncertaintyBased() { return UNCERTAINTY_BASED_ROUNDING_RULE; } diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 1afaf33..8be58f5 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -81,7 +81,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { */ private static final class JComboBoxItemSet extends AbstractSet { private final JComboBox comboBox; - + /** * @param comboBox combo box to get items from * @since 2022-02-19 @@ -90,35 +90,34 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { public JComboBoxItemSet(JComboBox comboBox) { this.comboBox = comboBox; } - + @Override public Iterator 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()) return JComboBoxItemSet.this.comboBox.getItemAt(this.index++); - else - throw new NoSuchElementException( - "Iterator has finished iteration"); + throw new NoSuchElementException( + "Iterator has finished iteration"); } }; } - + @Override public int size() { return this.comboBox.getItemCount(); } - + } - + /** * The standard types of rounding, corresponding to the options on the * TabbedView's settings panel. @@ -126,7 +125,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { * @since 2022-04-18 * @since v0.4.0 */ - private static enum StandardRoundingType { + private enum StandardRoundingType { /** * Rounds to a fixed number of significant digits. Precision is used, * representing the number of significant digits to round to. @@ -143,7 +142,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { */ UNCERTAINTY; } - + /** * Creates a TabbedView. * @@ -157,14 +156,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 dimensionSelector; @@ -178,7 +177,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; @@ -188,7 +187,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 unitNameList; @@ -198,18 +197,18 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { private final JTextArea unitTextBox; /** The text box for prefix data in the prefix viewer */ private final JTextArea prefixTextBox; - + // INFO & SETTINGS STUFF final JTextArea infoTextArea; private final JComboBox localeSelector; private StandardRoundingType roundingType; private int precision; - + private final Map> localizedTextSetters; - + /** * Creates the view and makes it visible to the user - * + * * @since 2022-02-19 * @since v0.4.0 */ @@ -223,180 +222,183 @@ 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 (Unlocalized)"); 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); - + this.localizedTextSetters = new HashMap<>(); - + // ============ UNIT CONVERSION TAB ============ - final JPanel convertUnitPanel = new JPanel(); + final var convertUnitPanel = new JPanel(); this.masterPane.addTab("Convert Units", convertUnitPanel); this.localizedTextSetters.put("tv.convert_units.title", txt -> this.masterPane.setTitleAt(0, txt)); this.masterPane.setMnemonicAt(0, KeyEvent.VK_U); convertUnitPanel.setLayout(new BorderLayout()); - + { // panel for input part - final JPanel inputPanel = new JPanel(); + final var 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(); + + final var 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("-->"); + + final var 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(); + final var 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(); - this.localizedTextSetters.put("tv.convert_units.value_prompt", + + final var valuePrompt = new JLabel(); + this.localizedTextSetters.put("tv.convert_units.value_prompt", valuePrompt::setText); outputPanel.add(valuePrompt, BorderLayout.LINE_START); - + this.valueInput = new JTextField(); outputPanel.add(this.valueInput, BorderLayout.CENTER); - + // conversion button this.convertUnitButton = new JButton("Convert"); - this.localizedTextSetters.put("tv.convert_units.convert_btn", + this.localizedTextSetters.put("tv.convert_units.convert_btn", this.convertUnitButton::setText); 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(); + final var convertExpressionPanel = new JPanel(); this.masterPane.addTab("Convert Unit Expressions", convertExpressionPanel); - this.localizedTextSetters.put("tv.convert_expressions.title", + this.localizedTextSetters.put("tv.convert_expressions.title", txt -> this.masterPane.setTitleAt(1, txt)); 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.localizedTextSetters.put("tv.convert_expressions.from", - txt -> this.fromEntry.setBorder(BorderFactory.createTitledBorder(txt))); - + this.localizedTextSetters.put("tv.convert_expressions.from", + txt -> this.fromEntry + .setBorder(BorderFactory.createTitledBorder(txt))); + this.toEntry = new JTextField(); convertExpressionPanel.add(this.toEntry); - this.localizedTextSetters.put("tv.convert_expressions.to", - txt -> this.toEntry.setBorder(BorderFactory.createTitledBorder(txt))); - + this.localizedTextSetters.put("tv.convert_expressions.to", + txt -> this.toEntry + .setBorder(BorderFactory.createTitledBorder(txt))); + // button to convert this.convertExpressionButton = new JButton(); this.localizedTextSetters.put("tv.convert_expressions.convert_btn", this.convertExpressionButton::setText); 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.localizedTextSetters.put("tv.convert_expressions.output", - txt -> this.expressionOutput.setBorder(BorderFactory.createTitledBorder(txt))); + this.localizedTextSetters.put("tv.convert_expressions.output", + txt -> this.expressionOutput + .setBorder(BorderFactory.createTitledBorder(txt))); this.expressionOutput.setEditable(false); - + // =========== UNIT VIEWER =========== - final JPanel unitLookupPanel = new JPanel(); + final var unitLookupPanel = new JPanel(); this.masterPane.addTab("Unit Viewer", unitLookupPanel); - this.localizedTextSetters.put("tv.unit_viewer.title", + this.localizedTextSetters.put("tv.unit_viewer.title", txt -> this.masterPane.setTitleAt(2, txt)); 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(); + final var prefixLookupPanel = new JPanel(); this.masterPane.addTab("Prefix Viewer", prefixLookupPanel); - this.localizedTextSetters.put("tv.prefix_viewer.title", + this.localizedTextSetters.put("tv.prefix_viewer.title", txt -> this.masterPane.setTitleAt(3, txt)); 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(); + + final var infoPanel = new JPanel(); this.masterPane.addTab("\uD83D\uDEC8", // info (i) character new JScrollPane(infoPanel)); - + this.infoTextArea = new JTextArea(); this.infoTextArea.setEditable(false); this.infoTextArea.setOpaque(false); infoPanel.add(this.infoTextArea); - + // ============ SETTINGS PANEL ============ this.localeSelector = new JComboBox<>(); this.masterPane.addTab("\u2699", new JScrollPane(this.createSettingsPanel())); this.masterPane.setMnemonicAt(5, KeyEvent.VK_S); - + // ============ FINALIZE CREATION OF VIEW ============ this.presenter.postViewInitialize(); this.updateText(); 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!) @@ -405,64 +407,64 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { * @since v0.4.0 */ private JPanel createSettingsPanel() { - final JPanel settingsPanel = new JPanel(); - + final var settingsPanel = new JPanel(); + settingsPanel .setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS)); - + // ============ ROUNDING SETTINGS ============ { - final JPanel roundingPanel = new JPanel(); + final var roundingPanel = new JPanel(); settingsPanel.add(roundingPanel); - this.localizedTextSetters.put("tv.settings.rounding.title", + this.localizedTextSetters.put("tv.settings.rounding.title", txt -> roundingPanel.setBorder(new TitledBorder(txt))); roundingPanel.setLayout(new GridBagLayout()); - + // rounding rule selection - final ButtonGroup roundingRuleButtons = new ButtonGroup(); + final var 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(); - this.localizedTextSetters.put("tv.settings.rounding.rule", + + final var roundingRuleLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.rounding.rule", roundingRuleLabel::setText); 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(); - this.localizedTextSetters.put("tv.settings.rounding.precision", + final var sliderLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.rounding.precision", sliderLabel::setText); sliderLabel.setVisible( this.roundingType != StandardRoundingType.UNCERTAINTY); roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4) .setAnchor(GridBagConstraints.LINE_START).build()); - - final JSlider sigDigSlider = new JSlider(0, 12); + + final var 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(); - this.localizedTextSetters.put("tv.settings.rounding.fixed_sigfig", + final var fixedPrecision = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.fixed_sigfig", fixedPrecision::setText); if (this.roundingType == StandardRoundingType.SIGNIFICANT_DIGITS) { fixedPrecision.setSelected(true); @@ -476,10 +478,10 @@ 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(); - this.localizedTextSetters.put("tv.settings.rounding.fixed_places", + final var fixedDecimals = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.fixed_places", fixedDecimals::setText); if (this.roundingType == StandardRoundingType.DECIMAL_PLACES) { fixedDecimals.setSelected(true); @@ -493,10 +495,10 @@ 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(); - this.localizedTextSetters.put("tv.settings.rounding.uncertainty", + final var relativePrecision = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.uncertainty", relativePrecision::setText); if (this.roundingType == StandardRoundingType.UNCERTAINTY) { relativePrecision.setSelected(true); @@ -511,24 +513,24 @@ 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(); + final var prefixRepetitionPanel = new JPanel(); settingsPanel.add(prefixRepetitionPanel); - this.localizedTextSetters.put("tv.settings.repetition.title", + this.localizedTextSetters.put("tv.settings.repetition.title", txt -> prefixRepetitionPanel.setBorder(new TitledBorder(txt))); 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(); - this.localizedTextSetters.put("tv.settings.repetition.no", + final var prefixRuleButtons = new ButtonGroup(); + + final var noRepetition = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.no", noRepetition::setText); if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) { noRepetition.setSelected(true); @@ -541,9 +543,9 @@ 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(); - this.localizedTextSetters.put("tv.settings.repetition.any", + + final var noRestriction = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.any", noRestriction::setText); if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) { noRestriction.setSelected(true); @@ -556,9 +558,9 @@ 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(); - this.localizedTextSetters.put("tv.settings.repetition.complex", + + final var customRepetition = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.complex", customRepetition::setText); if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) { customRepetition.setSelected(true); @@ -572,22 +574,22 @@ 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(); + final var searchingPanel = new JPanel(); settingsPanel.add(searchingPanel); - this.localizedTextSetters.put("tv.settings.search.title", + this.localizedTextSetters.put("tv.settings.search.title", txt -> searchingPanel.setBorder(new TitledBorder(txt))); searchingPanel.setLayout(new GridBagLayout()); - + // searching rules - final ButtonGroup searchRuleButtons = new ButtonGroup(); - + final var searchRuleButtons = new ButtonGroup(); + final var searchRule = this.presenter.getSearchRule(); - - final JRadioButton noPrefixes = new JRadioButton(); - this.localizedTextSetters.put("tv.settings.search.no_prefixes", + + final var noPrefixes = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.no_prefixes", noPrefixes::setText); noPrefixes.addActionListener(e -> { this.presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES); @@ -597,9 +599,9 @@ 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(); - this.localizedTextSetters.put("tv.settings.search.common_prefixes", + + final var commonPrefixes = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.common_prefixes", commonPrefixes::setText); commonPrefixes.addActionListener(e -> { this.presenter.setSearchRule(PrefixSearchRule.COMMON_PREFIXES); @@ -609,9 +611,9 @@ 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(); - this.localizedTextSetters.put("tv.settings.search.all_prefixes", + + final var alwaysInclude = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.all_prefixes", alwaysInclude::setText); alwaysInclude.addActionListener(e -> { this.presenter @@ -622,7 +624,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)) { @@ -634,14 +636,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.presenter.saveSettings(); } } - + // ============ OTHER SETTINGS ============ { - final JPanel miscPanel = new JPanel(); + final var miscPanel = new JPanel(); settingsPanel.add(miscPanel); miscPanel.setLayout(new GridBagLayout()); - - final JCheckBox oneWay = new JCheckBox(); + + final var oneWay = new JCheckBox(); this.localizedTextSetters.put("tv.settings.oneway", oneWay::setText); oneWay.setSelected(this.presenter.oneWayConversionEnabled()); oneWay.addItemListener(e -> { @@ -651,9 +653,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { }); miscPanel.add(oneWay, new GridBagBuilder(0, 0, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - - final JCheckBox showAllVariations = new JCheckBox(); - this.localizedTextSetters.put("tv.settings.show_duplicate", + + final var showAllVariations = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.show_duplicate", showAllVariations::setText); showAllVariations.setSelected(this.presenter.duplicatesShown()); showAllVariations.addItemListener(e -> { @@ -663,25 +665,25 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { }); miscPanel.add(showAllVariations, new GridBagBuilder(0, 1, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - - final JCheckBox useDefaultFiles = new JCheckBox(); - this.localizedTextSetters.put("tv.settings.use_default_files", + + final var useDefaultFiles = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.use_default_files", useDefaultFiles::setText); useDefaultFiles.setSelected(this.presenter.usingDefaultDatafiles()); useDefaultFiles.addItemListener(e -> { - this.presenter - .setUseDefaultDatafiles(e.getStateChange() == ItemEvent.SELECTED); + this.presenter.setUseDefaultDatafiles( + e.getStateChange() == ItemEvent.SELECTED); this.presenter.saveSettings(); }); miscPanel.add(useDefaultFiles, new GridBagBuilder(0, 2, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - - final JLabel localeLabel = new JLabel(); - this.localizedTextSetters.put("tv.settings.locale", + + final var localeLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.locale", localeLabel::setText); miscPanel.add(localeLabel, new GridBagBuilder(0, 3, 1, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - + this.presenter.getAvailableLocales().stream().sorted() .forEachOrdered(this.localeSelector::addItem); this.localeSelector.setSelectedItem(this.presenter.getUserLocale()); @@ -691,51 +693,51 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { }); miscPanel.add(localeSelector, new GridBagBuilder(1, 3, 1, 1) .setAnchor(GridBagConstraints.LINE_END).build()); - - final JButton unitFileButton = new JButton(); - this.localizedTextSetters.put("tv.settings.unitfiles.button", + + final var unitFileButton = new JButton(); + this.localizedTextSetters.put("tv.settings.unitfiles.button", unitFileButton::setText); unitFileButton.setEnabled(false); miscPanel.add(unitFileButton, new GridBagBuilder(0, 4, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); } - + return settingsPanel; } - + @Override public Set getDimensionNames() { return Collections .unmodifiableSet(new JComboBoxItemSet<>(this.dimensionSelector)); } - + @Override public String getFromExpression() { return this.fromEntry.getText(); } - + @Override public Optional getFromSelection() { return this.fromSearch.getSelectedValue(); } - + @Override public Set 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 @@ -748,14 +750,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { return OptionalInt .of(((StandardDisplayRules.FixedDecimals) presenterRule) .decimalPlaces()); - else if (presenterRule instanceof StandardDisplayRules.FixedPrecision) + if (presenterRule instanceof StandardDisplayRules.FixedPrecision) return OptionalInt .of(((StandardDisplayRules.FixedPrecision) presenterRule) .significantFigures()); - else - return OptionalInt.empty(); + return OptionalInt.empty(); } - + /** * @return presenter's prefix repetition rule * @since 2022-04-19 @@ -767,7 +768,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { ? Optional.of((DefaultPrefixRepetitionRule) prefixRule) : Optional.empty(); } - + /** * Determines which rounding type the presenter is currently using, if any. * @@ -779,48 +780,47 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { if (Objects.equals(presenterRule, StandardDisplayRules.uncertaintyBased())) return Optional.of(StandardRoundingType.UNCERTAINTY); - else if (presenterRule instanceof StandardDisplayRules.FixedDecimals) + if (presenterRule instanceof StandardDisplayRules.FixedDecimals) return Optional.of(StandardRoundingType.DECIMAL_PLACES); - else if (presenterRule instanceof StandardDisplayRules.FixedPrecision) + if (presenterRule instanceof StandardDisplayRules.FixedPrecision) return Optional.of(StandardRoundingType.SIGNIFICANT_DIGITS); - else - return Optional.empty(); + return Optional.empty(); } - + @Override public Optional getSelectedDimensionName() { - final String selectedItem = (String) this.dimensionSelector + final var selectedItem = (String) this.dimensionSelector .getSelectedItem(); return Optional.ofNullable(selectedItem); } - + @Override public String getToExpression() { return this.toEntry.getText(); } - + @Override public Optional getToSelection() { return this.toSearch.getSelectedValue(); } - + @Override public Set 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 getViewedPrefixName() { return this.prefixNameList.getSelectedValue(); } - + @Override public Optional getViewedUnitName() { return this.unitNameList.getSelectedValue(); } - + @Override public void setDimensionNames(Set dimensionNames) { this.dimensionSelector.removeAllItems(); @@ -828,44 +828,44 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.dimensionSelector.addItem(d); } } - + @Override public void setFromUnitNames(Set units) { this.fromSearch.setItems(units); } - + @Override public void setToUnitNames(Set units) { this.toSearch.setItems(units); } - + @Override public void setViewablePrefixNames(Set prefixNames) { this.prefixNameList.setItems(prefixNames); } - + @Override public void setViewableUnitNames(Set 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(uc.toString()); } - + @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) { @@ -873,16 +873,16 @@ 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 - * + * * @since 2022-04-18 * @since v0.4.0 */ @@ -910,7 +910,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.frame.setTitle(this.presenter.getLocalizedText("tv.title") .replace("[v]", ProgramInfo.VERSION.toString())); this.infoTextArea.setText(this.presenter.getAboutText()); - this.localizedTextSetters.forEach((id, action) -> - action.accept(this.presenter.getLocalizedText(id))); + this.localizedTextSetters.forEach( + (id, action) -> action.accept(this.presenter.getLocalizedText(id))); } } diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java index 958deae..3c2bb6c 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,16 +78,12 @@ public final class UnitConversionRecord { return new UnitConversionRecord(fromName, toName, inputValueString, outputValueString); } - - /** - * The name of the unit or expression that was converted from - */ + + /** The name of the unit or expression that was converted from */ private final String fromName; - /** - * The name of the unit or expression that was converted to - */ + /** 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 +94,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 @@ -115,14 +111,14 @@ public final class UnitConversionRecord { this.inputValueString = inputValueString; this.outputValueString = outputValueString; } - + @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof UnitConversionRecord)) return false; - final UnitConversionRecord other = (UnitConversionRecord) obj; + final var other = (UnitConversionRecord) obj; if (this.fromName == null) { if (other.fromName != null) return false; @@ -145,7 +141,7 @@ public final class UnitConversionRecord { return false; return true; } - + /** * @return name of unit or expression that was converted from * @since 2022-04-09 @@ -154,11 +150,11 @@ public final class UnitConversionRecord { public String fromName() { return this.fromName; } - + @Override public int hashCode() { - final int prime = 31; - int result = 1; + final var prime = 31; + var result = 1; result = prime * result + (this.fromName == null ? 0 : this.fromName.hashCode()); result = prime * result + (this.inputValueString == null ? 0 @@ -169,7 +165,7 @@ public final class UnitConversionRecord { + (this.toName == null ? 0 : this.toName.hashCode()); return result; } - + /** * @return string representing input value * @since 2022-04-09 @@ -178,7 +174,7 @@ public final class UnitConversionRecord { public String inputValueString() { return this.inputValueString; } - + /** * @return string representing output value * @since 2022-04-09 @@ -187,7 +183,7 @@ public final class UnitConversionRecord { public String outputValueString() { return this.outputValueString; } - + /** * @return name of unit or expression that was converted to * @since 2022-04-09 @@ -196,12 +192,12 @@ public final class UnitConversionRecord { public String toName() { return this.toName; } - + @Override public String toString() { - final String inputString = this.inputValueString.isBlank() ? this.fromName + final var inputString = this.inputValueString.isBlank() ? this.fromName : this.inputValueString + " " + this.fromName; - final String outputString = this.outputValueString.isBlank() ? this.toName + final var outputString = this.outputValueString.isBlank() ? this.toName : this.outputValueString + " " + this.toName; return inputString + " = " + outputString; } diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index c7ffda4..fa3a388 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -21,7 +21,7 @@ import java.util.Set; /** * A View that supports single unit-based conversion - * + * * @author Adrien Hopkins * @since 2021-12-15 * @since v0.4.0 @@ -110,7 +110,7 @@ public interface UnitConversionView extends View { /** * Shows the output of a unit conversion. - * + * * @param uc record of unit conversion * @since 2021-12-24 * @since v0.4.0 diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index fc04593..0adeb3a 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -24,7 +24,7 @@ import sevenUnits.utils.NameSymbol; /** * An object that controls user interaction with 7Units - * + * * @author Adrien Hopkins * @since 2021-12-15 * @since v0.4.0 @@ -112,10 +112,10 @@ public interface View { */ void showUnit(NameSymbol name, String definition, String dimensionName, UnitType type); - + /** * Updates the view's text to reflect the presenter's locale. - * + * * This method must not call {@link Presenter#setUserLocale(String)}. */ void updateText(); diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index 689b460..750e2d9 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -30,7 +30,7 @@ import sevenUnits.utils.Nameable; /** * A class that simulates a View (supports both unit and expression conversion) * for testing. Getters and setters work as expected. - * + * * @author Adrien Hopkins * @since 2022-01-29 * @since v0.4.0 @@ -66,7 +66,7 @@ public final class ViewBot return true; if (!(obj instanceof PrefixViewingRecord)) return false; - final PrefixViewingRecord other = (PrefixViewingRecord) obj; + final var other = (PrefixViewingRecord) obj; return Objects.equals(this.multiplierString, other.multiplierString) && Objects.equals(this.nameSymbol, other.nameSymbol); } @@ -93,7 +93,7 @@ public final class ViewBot @Override public String toString() { - final StringBuilder builder = new StringBuilder(); + final var builder = new StringBuilder(); builder.append("PrefixViewingRecord [nameSymbol="); builder.append(this.nameSymbol); builder.append(", multiplierString="); @@ -156,7 +156,7 @@ public final class ViewBot return true; if (!(obj instanceof UnitViewingRecord)) return false; - final UnitViewingRecord other = (UnitViewingRecord) obj; + final var other = (UnitViewingRecord) obj; return Objects.equals(this.definition, other.definition) && Objects.equals(this.dimensionName, other.dimensionName) && Objects.equals(this.nameSymbol, other.nameSymbol) @@ -186,7 +186,7 @@ public final class ViewBot @Override public String toString() { - final StringBuilder builder = new StringBuilder(); + final var builder = new StringBuilder(); builder.append("UnitViewingRecord [nameSymbol="); builder.append(this.nameSymbol); builder.append(", definition="); @@ -424,6 +424,7 @@ public final class ViewBot /** * Sets the view's selected dimension + * * @param selectedDimensionName name of dimension to select (string) */ public void setSelectedDimensionName(String selectedDimensionName) { @@ -453,9 +454,7 @@ public final class ViewBot "toSelection cannot be null."); } - /** - * @param toSelection unit set in the 'To' selection - */ + /** @param toSelection unit set in the 'To' selection */ public void setToSelection(String toSelection) { this.setToSelection(Optional.of(toSelection)); } @@ -475,9 +474,7 @@ public final class ViewBot // do nothing, ViewBot supports selecting any unit } - /** - * @param viewedPrefixName name of prefix being used - */ + /** @param viewedPrefixName name of prefix being used */ public void setViewedPrefixName(Optional viewedPrefixName) { this.prefixViewerSelection = viewedPrefixName; } @@ -490,9 +487,7 @@ public final class ViewBot this.setViewedPrefixName(Optional.of(viewedPrefixName)); } - /** - * @param viewedUnitName name of unit being used - */ + /** @param viewedUnitName name of unit being used */ public void setViewedUnitName(Optional viewedUnitName) { this.unitViewerSelection = viewedUnitName; } diff --git a/src/main/java/sevenUnitsGUI/package-info.java b/src/main/java/sevenUnitsGUI/package-info.java index 74ec18c..9432960 100644 --- a/src/main/java/sevenUnitsGUI/package-info.java +++ b/src/main/java/sevenUnitsGUI/package-info.java @@ -16,7 +16,7 @@ */ /** * The MVP GUI of SevenUnits - * + * * @author Adrien Hopkins * @since 2021-12-15 * @since v0.4.0 diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 800d13d..3d6d663 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -58,7 +58,6 @@ class UnitDatabaseTest { private V value; /** - * * @since 2021-10-07 * @since v0.3.2 */ @@ -190,7 +189,7 @@ class UnitDatabaseTest { } private static final Stream testEvaluateExpressionValid() { - UncertainDouble uncertainTwoThirds = UncertainDouble.of(2.0, 1.0) + final var uncertainTwoThirds = UncertainDouble.of(2.0, 1.0) .dividedBy(UncertainDouble.of(3.0, 1.0)); return Stream.of( Arguments.of("J + (2 * 3) J + (20 / 4) J", @@ -206,19 +205,14 @@ class UnitDatabaseTest { Arguments.of("2 J / 3 J", LinearUnitValue.of(J.dividedBy(J), uncertainTwoThirds))); } - + private static final Stream testFormatExpression() { - return Stream.of( - Arguments.of("1*2", "1 * 2"), - Arguments.of("1/2", "1 / 2"), - Arguments.of("1|2", "1 | 2"), - Arguments.of("1^2", "1 ^ 2"), - Arguments.of("1 * 2", "1 * 2"), - Arguments.of("+1", "+1"), - Arguments.of("-1", "-1"), + return Stream.of(Arguments.of("1*2", "1 * 2"), + Arguments.of("1/2", "1 / 2"), Arguments.of("1|2", "1 | 2"), + Arguments.of("1^2", "1 ^ 2"), Arguments.of("1 * 2", "1 * 2"), + Arguments.of("+1", "+1"), Arguments.of("-1", "-1"), Arguments.of("1.1e+5", "1.1e+5"), - Arguments.of("1.25e-5", "1.25e-5") - ); + Arguments.of("1.25e-5", "1.25e-5")); } /** @@ -266,15 +260,14 @@ class UnitDatabaseTest { database.addPrefix("B", B); database.addPrefix("C", C); - final var actual = database - .evaluateUnitExpression(expression); + final var actual = database.evaluateUnitExpression(expression); assertEquals(expected, actual); final var expectedU = expected.getUnit().times(expected.getValueExact()); final var actualU = database.getUnitFromExpression(expression); assertEquals(expectedU, actualU); } - + @ParameterizedTest @MethodSource public void testFormatExpression(String expression, String expected) { @@ -335,8 +328,7 @@ class UnitDatabaseTest { infiniteDatabase.addPrefix("B", B); infiniteDatabase.addPrefix("C", C); - final var entrySet = infiniteDatabase.unitMap() - .entrySet(); + final var entrySet = infiniteDatabase.unitMap().entrySet(); final var keySet = infiniteDatabase.unitMap().keySet(); assertThrows(IllegalStateException.class, () -> entrySet.toArray()); assertThrows(IllegalStateException.class, () -> keySet.toArray()); @@ -376,8 +368,7 @@ class UnitDatabaseTest { @ValueSource(ints = { 1, 2, 3, 4, 5 }) public void testLoadingInvalidUnitFile(int num) { final var database = new UnitDatabase(); - final var filename = String.format("/test-unitsfile-invalid%d.txt", - num); + final var filename = String.format("/test-unitsfile-invalid%d.txt", num); final var errs = loadUnitsFile(database, filename); assertFalse(errs.isEmpty(), "no error from invalid file " + filename); final var e = errs.get(0).problem(); @@ -418,7 +409,7 @@ class UnitDatabaseTest { assertEquals(7, database.getPrefix("A").getMultiplier()); assertEquals(11, database.getPrefix("B").getMultiplier()); assertEquals(13, database.getPrefix("C").getMultiplier()); - + // test invalid prefixes assertThrows(NoSuchElementException.class, () -> database.getPrefix("N/A")); @@ -487,8 +478,7 @@ class UnitDatabaseTest { final var map1 = database1.unitMap(); final var keyIterator1 = map1.keySet().iterator(); - final var entryIterator1 = map1.entrySet() - .iterator(); + final var entryIterator1 = map1.entrySet().iterator(); final Set expectedKeys = Set.of("U", "V", "W"); final Set actualKeys = new HashSet<>(); @@ -552,8 +542,7 @@ class UnitDatabaseTest { @Test public void testPrefixlessUnitMap() { final var database = new UnitDatabase(); - final var prefixlessUnits = database - .unitMapPrefixless(true); + final var prefixlessUnits = database.unitMapPrefixless(true); database.addUnit("U", U); database.addUnit("V", V); @@ -690,10 +679,8 @@ class UnitDatabaseTest { final var NUM_UNITS = database.unitMapPrefixless(true).size(); final var NUM_PREFIXES = database.prefixMap(true).size(); - final var nameIterator = database.unitMap().keySet() - .iterator(); - final var entryIterator = database.unitMap() - .entrySet().iterator(); + final var nameIterator = database.unitMap().keySet().iterator(); + final var entryIterator = database.unitMap().entrySet().iterator(); var expectedLength = 1; var unitsWithThisLengthSoFar = 0; diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index 4d9a103..7ae550f 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -32,7 +32,7 @@ import sevenUnits.utils.NameSymbol; /** * Testing the various Unit classes. This is NOT part of this program's public * API. - * + * * @author Adrien Hopkins * @since 2018-12-22 * @since v0.1.0 @@ -40,119 +40,121 @@ import sevenUnits.utils.NameSymbol; class UnitTest { /** A random number generator */ private static final Random rng = ThreadLocalRandom.current(); - + @Test public void testAdditionAndSubtraction() { - final LinearUnit inch = Metric.METRE.times(0.0254) + final var inch = Metric.METRE.times(0.0254) .withName(NameSymbol.of("inch", "in")); - final LinearUnit foot = Metric.METRE.times(0.3048) + final var foot = Metric.METRE.times(0.3048) .withName(NameSymbol.of("foot", "ft")); - - assertTrue(inch.plus(foot).equalsApproximately(Metric.METRE.times(0.3302)), - String.format("Expected: %s, Actual: %s", - inch.plus(foot), Metric.METRE.times(0.3302))); - assertTrue(foot.minus(inch).equalsApproximately(Metric.METRE.times(0.2794)), - String.format("Expected: %s, Actual: %s", - foot.minus(inch), Metric.METRE.times(0.2794))); - + + assertTrue( + inch.plus(foot).equalsApproximately(Metric.METRE.times(0.3302)), + String.format("Expected: %s, Actual: %s", inch.plus(foot), + Metric.METRE.times(0.3302))); + assertTrue( + foot.minus(inch).equalsApproximately(Metric.METRE.times(0.2794)), + String.format("Expected: %s, Actual: %s", foot.minus(inch), + Metric.METRE.times(0.2794))); + // test with LinearUnitValue - final LinearUnitValue value1 = LinearUnitValue.getExact(Metric.METRE, 15); - final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120); - final LinearUnitValue value3 = LinearUnitValue.getExact(Metric.METRE, + final var value1 = LinearUnitValue.getExact(Metric.METRE, 15); + final var value2 = LinearUnitValue.getExact(foot, 120); + final var value3 = LinearUnitValue.getExact(Metric.METRE, 0.5); - final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM, + final var value4 = LinearUnitValue.getExact(Metric.KILOGRAM, 60); - + // make sure addition is done correctly assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001); assertEquals(15.5, value1.plus(value3).getValueExact()); assertEquals(52.076, value1.plus(value2).plus(value3).getValueExact(), 0.001); - + // make sure addition uses the correct unit, and is still associative // (ignoring floating-point rounding errors) assertEquals(Metric.METRE, value1.plus(value2).getUnit()); assertEquals(Metric.METRE, value1.plus(value2).plus(value3).getUnit()); assertEquals(foot, value2.plus(value1).getUnit()); assertTrue(value1.plus(value2).equals(value2.plus(value1), true)); - + // make sure errors happen when they should assertThrows(IllegalArgumentException.class, () -> value1.plus(value4)); assertThrows(IllegalArgumentException.class, () -> value1.minus(value4)); } - + @Test public void testConversion() { - final LinearUnit metre = Metric.METRE; + final var metre = Metric.METRE; final Unit inch = metre.times(0.0254); - - final UnitValue value = UnitValue.of(inch, 75); - + + final var value = UnitValue.of(inch, 75); + assertEquals(1.9, inch.convertTo(metre, 75), 0.01); assertEquals(1.9, value.convertTo(metre).getValue(), 0.01); - + // try random stuff - for (int i = 0; i < 1000; i++) { + for (var i = 0; i < 1000; i++) { // initiate random values - final double conversionFactor = UnitTest.rng.nextDouble() * 1000000; - final double testValue = UnitTest.rng.nextDouble() * 1000000; - final double expected = testValue * conversionFactor; - + final var conversionFactor = UnitTest.rng.nextDouble() * 1000000; + final var testValue = UnitTest.rng.nextDouble() * 1000000; + final var expected = testValue * conversionFactor; + // test final Unit unit = Metric.METRE.times(conversionFactor); - final double actual = unit.convertToBase(testValue); - + final var actual = unit.convertToBase(testValue); + assertEquals(actual, expected, expected * DecimalComparison.DOUBLE_EPSILON); } } - + @Test public void testEquals() { - final LinearUnit metre = Metric.METRE; + final var metre = Metric.METRE; final Unit meter = Metric.BaseUnits.METRE.asLinearUnit(); - + assertEquals(metre, meter); } - + @Test public void testIsMetric() { final Unit metre = Metric.METRE; final Unit megasecond = Metric.SECOND.withPrefix(Metric.MEGA); final Unit hour = Metric.HOUR; - + assertTrue(metre.isMetric()); assertTrue(megasecond.isMetric()); assertFalse(hour.isMetric()); } - + @Test public void testMultiplicationAndDivision() { // test unit-times-unit multiplication - final LinearUnit generatedJoule = Metric.KILOGRAM + final var generatedJoule = Metric.KILOGRAM .times(Metric.METRE.toExponent(2)) .dividedBy(Metric.SECOND.toExponent(2)); - final LinearUnit actualJoule = Metric.JOULE; - + final var actualJoule = Metric.JOULE; + assertEquals(generatedJoule, actualJoule); - + // test multiplication by conversion factors - final LinearUnit kilometre = Metric.METRE.times(1000); - final LinearUnit hour = Metric.SECOND.times(3600); - final LinearUnit generatedKPH = kilometre.dividedBy(hour); - - final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND) + final var kilometre = Metric.METRE.times(1000); + final var hour = Metric.SECOND.times(3600); + final var generatedKPH = kilometre.dividedBy(hour); + + final var actualKPH = Metric.METRE.dividedBy(Metric.SECOND) .dividedBy(3.6); - + assertEquals(generatedKPH, actualKPH); } - + @Test public void testPrefixes() { - final LinearUnit generatedKilometre = Metric.METRE + final var generatedKilometre = Metric.METRE .withPrefix(Metric.KILO); - final LinearUnit actualKilometre = Metric.METRE.times(1000); - + final var actualKilometre = Metric.METRE.times(1000); + assertEquals(generatedKilometre, actualKilometre); } } diff --git a/src/test/java/sevenUnits/unit/UnitValueTest.java b/src/test/java/sevenUnits/unit/UnitValueTest.java index 6182b20..3679703 100644 --- a/src/test/java/sevenUnits/unit/UnitValueTest.java +++ b/src/test/java/sevenUnits/unit/UnitValueTest.java @@ -36,7 +36,7 @@ import sevenUnits.utils.UncertainDouble; /** * Tests for the UnitValue and LinearUnitValue classes - * + * * @since v1.0.0 */ public final class UnitValueTest { diff --git a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java index ea96574..f203fad 100644 --- a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java +++ b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -38,7 +37,7 @@ import sevenUnits.utils.ConditionalExistenceCollections.ConditionalExistenceIter * normal operations on conditional existence collections and ensures that * elements that do not pass the existence condition are not included in the * results. - * + * * @author Adrien Hopkins * @since 2019-10-16 * @since v0.3.0 @@ -47,22 +46,22 @@ class ConditionalExistenceCollectionsTest { /** * The returned iterator ignores elements that don't start with "a". - * + * * @return test iterator * @since 2019-10-17 * @since v0.3.0 */ ConditionalExistenceIterator getTestIterator() { final List items = Arrays.asList("aa", "ab", "ba"); - final Iterator it = items.iterator(); - final ConditionalExistenceIterator cit = (ConditionalExistenceIterator) ConditionalExistenceCollections + final var it = items.iterator(); + final var cit = (ConditionalExistenceIterator) ConditionalExistenceCollections .conditionalExistenceIterator(it, s -> s.startsWith("a")); return cit; } /** * The returned map ignores mappings where the value is zero. - * + * * @return map to be used for test data * @since 2019-10-16 * @since v0.3.0 @@ -79,59 +78,49 @@ class ConditionalExistenceCollectionsTest { return conditionalMap; } - /** - * Test method for the ConditionalExistenceMap's containsKey method. - */ + /** Test method for the ConditionalExistenceMap's containsKey method. */ @Test void testContainsKeyObject() { - final Map map = this.getTestMap(); + final var map = this.getTestMap(); assertTrue(map.containsKey("one")); assertTrue(map.containsKey("ten")); assertFalse(map.containsKey("five")); assertFalse(map.containsKey("zero")); } - /** - * Test method for the ConditionalExistenceMap's containsValue method. - */ + /** Test method for the ConditionalExistenceMap's containsValue method. */ @Test void testContainsValueObject() { - final Map map = this.getTestMap(); + final var map = this.getTestMap(); assertTrue(map.containsValue(1)); assertTrue(map.containsValue(10)); assertFalse(map.containsValue(5)); assertFalse(map.containsValue(0)); } - /** - * Test method for the ConditionalExistenceMap's entrySet method. - */ + /** Test method for the ConditionalExistenceMap's entrySet method. */ @Test void testEntrySet() { - final Map map = this.getTestMap(); + final var map = this.getTestMap(); for (final Entry e : map.entrySet()) { assertTrue(e.getValue() != 0); } } - /** - * Test method for the ConditionalExistenceMap's get method. - */ + /** Test method for the ConditionalExistenceMap's get method. */ @Test void testGetObject() { - final Map map = this.getTestMap(); + final var map = this.getTestMap(); assertEquals(1, map.get("one")); assertEquals(10, map.get("ten")); assertEquals(null, map.get("five")); assertEquals(null, map.get("zero")); } - /** - * Test method for the ConditionalExistenceCollection's iterator. - */ + /** Test method for the ConditionalExistenceCollection's iterator. */ @Test void testIterator() { - final ConditionalExistenceIterator testIterator = this + final var testIterator = this .getTestIterator(); assertTrue(testIterator.hasNext); @@ -150,22 +139,18 @@ class ConditionalExistenceCollectionsTest { assertThrows(NoSuchElementException.class, testIterator::next); } - /** - * Test method for the ConditionalExistenceMap's keySet operation. - */ + /** Test method for the ConditionalExistenceMap's keySet operation. */ @Test void testKeySet() { - final Map map = this.getTestMap(); - assertFalse(map.keySet().contains("zero")); + final var map = this.getTestMap(); + assertFalse(map.containsKey("zero")); } - /** - * Test method for the ConditionalExistenceMap's values operation. - */ + /** Test method for the ConditionalExistenceMap's values operation. */ @Test void testValues() { - final Map map = this.getTestMap(); - assertFalse(map.values().contains(0)); + final var map = this.getTestMap(); + assertFalse(map.containsValue(0)); } } diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java index 72d3b19..2e0b4b0 100644 --- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java +++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.params.provider.MethodSource; /** * A test for the {@code ExpressionParser} class. This is NOT part of this * program's public API. - * + * * @author Adrien Hopkins * @since 2019-03-22 * @since v0.2.0 @@ -45,9 +45,7 @@ class ExpressionParserTest { .addUnaryOperator("recip", o1 -> 1 / o1, 3) .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 4).build(); - /** - * The expressions used in the expression parsing tests - */ + /** The expressions used in the expression parsing tests */ private static final List TEST_EXPRESSIONS = List.of( // test parsing of expressions "1 + 2 ^ 5 * 3", "(1 + 2) ^ 5 * 3", @@ -56,22 +54,9 @@ class ExpressionParserTest { // ensure it normally goes from left to right "1 + 2 + 3 + 4", "12 - 4 - 3", "12 - (4 - 3)", "1 / 2 + 3"); - /** - * The expected results for evaluating these expressions - */ + /** The expected results for evaluating these expressions */ private static final int[] RESULTS = { 97, 729, 133, 10, 5, 11, 3 }; - /** - * @return A stream of objects, where each one is an expression and the - * expected result - * @since 2021-09-27 - * @since v0.3.2 - */ - private static final Stream testParseExpressionData() { - return IntStream.range(0, TEST_EXPRESSIONS.size()) - .mapToObj(i -> Arguments.of(TEST_EXPRESSIONS.get(i), RESULTS[i])); - } - private static final Stream testConvertExpressionToRPN() { return Stream.of(Arguments.of("1 + 2 ^ 5 * 3", "1 2 5 ^ 3 * +"), Arguments.of("(1 + 2) ^ 5 * 3", "1 2 + 5 ^ 3 *"), @@ -91,18 +76,6 @@ class ExpressionParserTest { Arguments.of("1 + neg 2", "1 2 neg +")); } - private static final Stream testParseRPN() { - return Stream.of(Arguments.of("1 2 5 ^ 3 * +", 97), - Arguments.of("1 2 + 5 ^ 3 *", 729), - Arguments.of("12 5 * 3 2 3 * ^ 72 - 3 3 2 * + / +", 133), - Arguments.of("1 2 + 3 + 4 +", 10), Arguments.of("12 4 - 3 -", 5), - Arguments.of("12 4 3 - -", 11), Arguments.of("1 2 / 3 +", 3), - Arguments.of("12", 12), Arguments.of("2 3 * 4 +", 10), - Arguments.of("2 3 * 4 -", 2), Arguments.of("2 3 4 + *", 14), - Arguments.of("2 3 4 - *", -2), Arguments.of("2 neg", -2), - Arguments.of("1 2 neg +", -1)); - } - private static final Stream testInvalidExpression() { return Stream.of("+", "1 +", "1 + * 2", "1 (+ 1)", "neg"); } @@ -112,13 +85,26 @@ class ExpressionParserTest { } /** - * Test method for - * {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}. + * @return A stream of objects, where each one is an expression and the + * expected result + * @since 2021-09-27 + * @since v0.3.2 */ - @ParameterizedTest - @MethodSource("testParseExpressionData") - public void testParseExpression(String expression, int value) { - assertEquals(value, numberParser.parseExpression(expression)); + private static final Stream testParseExpressionData() { + return IntStream.range(0, TEST_EXPRESSIONS.size()) + .mapToObj(i -> Arguments.of(TEST_EXPRESSIONS.get(i), RESULTS[i])); + } + + private static final Stream testParseRPN() { + return Stream.of(Arguments.of("1 2 5 ^ 3 * +", 97), + Arguments.of("1 2 + 5 ^ 3 *", 729), + Arguments.of("12 5 * 3 2 3 * ^ 72 - 3 3 2 * + / +", 133), + Arguments.of("1 2 + 3 + 4 +", 10), Arguments.of("12 4 - 3 -", 5), + Arguments.of("12 4 3 - -", 11), Arguments.of("1 2 / 3 +", 3), + Arguments.of("12", 12), Arguments.of("2 3 * 4 +", 10), + Arguments.of("2 3 * 4 -", 2), Arguments.of("2 3 4 + *", 14), + Arguments.of("2 3 4 - *", -2), Arguments.of("2 neg", -2), + Arguments.of("1 2 neg +", -1)); } @ParameterizedTest @@ -138,15 +124,25 @@ class ExpressionParserTest { @ParameterizedTest @MethodSource - public void testParseRPN(String expressionRPN, int value) { - assertEquals(value, - numberParser.parseReversePolishExpression(expressionRPN)); + public void testInvalidRPN(String expressionRPN) { + assertThrows(RuntimeException.class, + () -> numberParser.parseReversePolishExpression(expressionRPN)); + } + + /** + * Test method for + * {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}. + */ + @ParameterizedTest + @MethodSource("testParseExpressionData") + public void testParseExpression(String expression, int value) { + assertEquals(value, numberParser.parseExpression(expression)); } @ParameterizedTest @MethodSource - public void testInvalidRPN(String expressionRPN) { - assertThrows(RuntimeException.class, - () -> numberParser.parseReversePolishExpression(expressionRPN)); + public void testParseRPN(String expressionRPN, int value) { + assertEquals(value, + numberParser.parseReversePolishExpression(expressionRPN)); } } diff --git a/src/test/java/sevenUnits/utils/NameSymbolTest.java b/src/test/java/sevenUnits/utils/NameSymbolTest.java index 3ae2448..f8843e0 100644 --- a/src/test/java/sevenUnits/utils/NameSymbolTest.java +++ b/src/test/java/sevenUnits/utils/NameSymbolTest.java @@ -33,37 +33,65 @@ import org.junit.jupiter.params.provider.MethodSource; /** * Tests for the {@link NameSymbol} class. - * + * * @since v1.0.0 */ class NameSymbolTest { private static Stream testEqualsHashCode() { return Stream.of( - Arguments.of(NameSymbol.ofName("test"), NameSymbol.ofName("test"), true), + Arguments.of(NameSymbol.ofName("test"), NameSymbol.ofName("test"), + true), Arguments.of(NameSymbol.ofName("a"), NameSymbol.ofName("b"), false), - Arguments.of(NameSymbol.ofSymbol("test"), NameSymbol.ofSymbol("test"), true), - Arguments.of(NameSymbol.ofSymbol("a"), NameSymbol.ofSymbol("b"), false), - Arguments.of(NameSymbol.ofName("test"), NameSymbol.ofSymbol("test"), false), - Arguments.of(NameSymbol.of("main", "s"), NameSymbol.of("main", "s"), true), - Arguments.of(NameSymbol.of("main", "s"), NameSymbol.of("main", "s", "m"), false), + Arguments.of(NameSymbol.ofSymbol("test"), + NameSymbol.ofSymbol("test"), true), + Arguments.of(NameSymbol.ofSymbol("a"), NameSymbol.ofSymbol("b"), + false), + Arguments.of(NameSymbol.ofName("test"), NameSymbol.ofSymbol("test"), + false), + Arguments.of(NameSymbol.of("main", "s"), NameSymbol.of("main", "s"), + true), + Arguments.of(NameSymbol.of("main", "s"), + NameSymbol.of("main", "s", "m"), false), Arguments.of(new NameSymbol(null, null, new HashSet<>()), new NameSymbol(null, null, new HashSet<>()), true), - Arguments.of(new NameSymbol( - Optional.of("main"), Optional.of("s"), new HashSet<>()), + Arguments.of( + new NameSymbol(Optional.of("main"), Optional.of("s"), + new HashSet<>()), new NameSymbol(null, null, new HashSet<>()), false), Arguments.of(new NameSymbol(null, null, new HashSet<>()), new NameSymbol(Optional.of("main"), Optional.of("s"), - new HashSet<>()), false), - Arguments.of(new NameSymbol(Optional.of("main"), null, new HashSet<>()), + new HashSet<>()), + false), + Arguments.of( + new NameSymbol(Optional.of("main"), null, new HashSet<>()), new NameSymbol(Optional.of("main"), Optional.of("s"), - new HashSet<>()), false)); + new HashSet<>()), + false)); + } + + @Test + public void testCreate() { + final Set names = Set.of("a", "b", "c"); + final var ns = NameSymbol.ofNullable(null, null, names); + assertTrue(ns.getPrimaryName().isPresent(), + "NameSymbol created without primary name."); + assertTrue(names.contains(ns.getPrimaryName().orElseThrow()), + String.format("Primary name (%s) was not obtained from names set.", + ns.getPrimaryName())); + assertFalse( + ns.getOtherNames().contains(ns.getPrimaryName().orElseThrow()), + String.format("Primary name (%s) was included in other names set.", + ns.getPrimaryName())); + assertEquals(Set.of("a", "b", "c"), names, + "names input was changed by ofNullable()"); } - + /** - * Tests that two NameSymbols are or are not equal. - * If they are equal, also ensures they have the same hash code. - * @param a first NameSymbol to test - * @param b second NameSymbol to test + * Tests that two NameSymbols are or are not equal. If they are equal, also + * ensures they have the same hash code. + * + * @param a first NameSymbol to test + * @param b second NameSymbol to test * @param equal true iff a should be equal to be, otherwise false */ @ParameterizedTest @@ -77,18 +105,4 @@ class NameSymbolTest { assertFalse(Objects.equals(a, b)); } } - - @Test - public void testCreate() { - Set names = Set.of("a", "b", "c"); - NameSymbol ns = NameSymbol.ofNullable(null, null, names); - assertTrue(ns.getPrimaryName().isPresent(), "NameSymbol created without primary name."); - assertTrue(names.contains(ns.getPrimaryName().orElseThrow()), - String.format("Primary name (%s) was not obtained from names set.", - ns.getPrimaryName())); - assertFalse(ns.getOtherNames().contains(ns.getPrimaryName().orElseThrow()), - String.format("Primary name (%s) was included in other names set.", - ns.getPrimaryName())); - assertEquals(Set.of("a", "b", "c"), names, "names input was changed by ofNullable()"); - } } diff --git a/src/test/java/sevenUnits/utils/ObjectProductTest.java b/src/test/java/sevenUnits/utils/ObjectProductTest.java index 584b3f3..7c5df88 100644 --- a/src/test/java/sevenUnits/utils/ObjectProductTest.java +++ b/src/test/java/sevenUnits/utils/ObjectProductTest.java @@ -34,7 +34,7 @@ import sevenUnits.unit.Metric; /** * Tests for {@link ObjectProduct} using BaseDimension as a test object. This is * NOT part of this program's public API. - * + * * @author Adrien Hopkins * @since 2018-12-12 * @since v0.1.0 @@ -42,7 +42,7 @@ import sevenUnits.unit.Metric; class ObjectProductTest { /** * Tests {@link UnitDimension#equals} - * + * * @since 2018-12-12 * @since v0.1.0 */ @@ -54,7 +54,7 @@ class ObjectProductTest { /** * Tests {@code UnitDimension}'s exponentiation - * + * * @since 2019-01-15 * @since v0.1.0 */ @@ -66,7 +66,7 @@ class ObjectProductTest { /** * Tests {@code UnitDimension}'s multiplication and division. - * + * * @since 2018-12-12 * @since v0.1.0 */ diff --git a/src/test/java/sevenUnits/utils/SemanticVersionTest.java b/src/test/java/sevenUnits/utils/SemanticVersionTest.java index 3bef773..047f0b5 100644 --- a/src/test/java/sevenUnits/utils/SemanticVersionTest.java +++ b/src/test/java/sevenUnits/utils/SemanticVersionTest.java @@ -40,7 +40,7 @@ import org.junit.jupiter.api.Test; public final class SemanticVersionTest { /** * Test for {@link SemanticVersionNumber#compatible} - * + * * @since 2022-02-20 * @since v0.4.0 */ @@ -66,32 +66,32 @@ public final class SemanticVersionTest { /** * Tests {@link SemanticVersionNumber#toString} for complex version numbers - * + * * @since 2022-02-19 * @since v0.4.0 */ @Test public void testComplexToString() { - final SemanticVersionNumber v1 = builder(1, 2, 3).preRelease(1, 2, 3) + final var v1 = builder(1, 2, 3).preRelease(1, 2, 3) .build(); assertEquals("1.2.3-1.2.3", v1.toString()); - final SemanticVersionNumber v2 = builder(4, 5, 6).preRelease("abc", 123) + final var v2 = builder(4, 5, 6).preRelease("abc", 123) .buildMetadata("2022-02-19").build(); assertEquals("4.5.6-abc.123+2022-02-19", v2.toString()); - final SemanticVersionNumber v3 = builder(1, 0, 0) + final var v3 = builder(1, 0, 0) .preRelease("x-y-z", "--").build(); assertEquals("1.0.0-x-y-z.--", v3.toString()); } /** * Tests that complex version can be created and their parts read - * + * * @since 2022-02-19 * @since v0.4.0 */ @Test public void testComplexVersions() { - final SemanticVersionNumber v1 = builder(1, 2, 3).preRelease(1, 2, 3) + final var v1 = builder(1, 2, 3).preRelease(1, 2, 3) .build(); assertEquals(1, v1.majorVersion()); assertEquals(2, v1.minorVersion()); @@ -99,7 +99,7 @@ public final class SemanticVersionTest { assertEquals(List.of("1", "2", "3"), v1.preReleaseIdentifiers()); assertEquals(List.of(), v1.buildMetadata()); - final SemanticVersionNumber v2 = builder(4, 5, 6).preRelease("abc", 123) + final var v2 = builder(4, 5, 6).preRelease("abc", 123) .buildMetadata("2022-02-19").build(); assertEquals(4, v2.majorVersion()); assertEquals(5, v2.minorVersion()); @@ -107,7 +107,7 @@ public final class SemanticVersionTest { assertEquals(List.of("abc", "123"), v2.preReleaseIdentifiers()); assertEquals(List.of("2022-02-19"), v2.buildMetadata()); - final SemanticVersionNumber v3 = builder(1, 0, 0) + final var v3 = builder(1, 0, 0) .preRelease("x-y-z", "--").build(); assertEquals(1, v3.majorVersion()); assertEquals(0, v3.minorVersion()); @@ -118,7 +118,7 @@ public final class SemanticVersionTest { /** * Test that semantic version strings can be parsed correctly - * + * * @since 2022-02-19 * @since v0.4.0 * @see SemanticVersionNumber#fromString @@ -158,9 +158,7 @@ public final class SemanticVersionTest { "Could not parse 1.2.3-abc.56.def+2022abc99"); } - /** - * Ensures it is impossible to create invalid version numbers - */ + /** Ensures it is impossible to create invalid version numbers */ @Test public void testInvalidVersionNumbers() { // stableVersion() @@ -204,7 +202,7 @@ public final class SemanticVersionTest { assertThrows(IllegalArgumentException.class, () -> builder(-3, 0, 7), "Negative major version number tolerated by builder"); - final SemanticVersionNumber.Builder testBuilder = builder(1, 2, 3); + final var testBuilder = builder(1, 2, 3); // note: builder.buildMetadata(null) doesn't even compile lol // builder.buildMetadata assertThrows(NullPointerException.class, @@ -270,7 +268,7 @@ public final class SemanticVersionTest { /** * Test for {@link SemanticVersionNumber#isStable} - * + * * @since 2022-02-19 * @since v0.4.0 */ @@ -294,30 +292,30 @@ public final class SemanticVersionTest { * {@link SemanticVersionNumber#compareTo} according to official rules. Tests * all of the versions compared in section 11 of the SemVer 2.0.0 document * and some more. - * + * * @since 2022-02-19 * @since v0.4.0 */ @Test public void testOrder() { - final SemanticVersionNumber v100a = builder(1, 0, 0).preRelease("alpha") + final var v100a = builder(1, 0, 0).preRelease("alpha") .build(); // 1.0.0-alpha - final SemanticVersionNumber v100a1 = preRelease(1, 0, 0, "alpha", 1); // 1.0.0-alpha.1 - final SemanticVersionNumber v100ab = builder(1, 0, 0) + final var v100a1 = preRelease(1, 0, 0, "alpha", 1); // 1.0.0-alpha.1 + final var v100ab = builder(1, 0, 0) .preRelease("alpha", "beta").build(); // 1.0.0-alpha.beta - final SemanticVersionNumber v100b = builder(1, 0, 0).preRelease("beta") + final var v100b = builder(1, 0, 0).preRelease("beta") .build(); // 1.0.0-alpha - final SemanticVersionNumber v100b2 = preRelease(1, 0, 0, "beta", 2); // 1.0.0-beta.2 - final SemanticVersionNumber v100b11 = preRelease(1, 0, 0, "beta", 11); // 1.0.0-beta.11 - final SemanticVersionNumber v100rc1 = preRelease(1, 0, 0, "rc", 1); // 1.0.0-rc.1 - final SemanticVersionNumber v100 = stableVersion(1, 0, 0); - final SemanticVersionNumber v100plus = builder(1, 0, 0) + final var v100b2 = preRelease(1, 0, 0, "beta", 2); // 1.0.0-beta.2 + final var v100b11 = preRelease(1, 0, 0, "beta", 11); // 1.0.0-beta.11 + final var v100rc1 = preRelease(1, 0, 0, "rc", 1); // 1.0.0-rc.1 + final var v100 = stableVersion(1, 0, 0); + final var v100plus = builder(1, 0, 0) .buildMetadata("blah", "blah", "blah").build(); // 1.0.0+blah.blah.blah - final SemanticVersionNumber v200 = stableVersion(2, 0, 0); - final SemanticVersionNumber v201 = stableVersion(2, 0, 1); - final SemanticVersionNumber v210 = stableVersion(2, 1, 0); - final SemanticVersionNumber v211 = stableVersion(2, 1, 1); - final SemanticVersionNumber v300 = stableVersion(3, 0, 0); + final var v200 = stableVersion(2, 0, 0); + final var v201 = stableVersion(2, 0, 1); + final var v210 = stableVersion(2, 1, 0); + final var v211 = stableVersion(2, 1, 1); + final var v300 = stableVersion(3, 0, 0); // test order of version numbers assertTrue(v100a.compareTo(v100a1) < 0, "1.0.0-alpha >= 1.0.0-alpha.1"); @@ -355,18 +353,18 @@ public final class SemanticVersionTest { /** * Tests that simple stable versions can be created and their parts read - * + * * @since 2022-02-19 * @since v0.4.0 */ @Test public void testSimpleStableVersions() { - final SemanticVersionNumber v100 = stableVersion(1, 0, 0); + final var v100 = stableVersion(1, 0, 0); assertEquals(1, v100.majorVersion()); assertEquals(0, v100.minorVersion()); assertEquals(0, v100.patchVersion()); - final SemanticVersionNumber v925 = stableVersion(9, 2, 5); + final var v925 = stableVersion(9, 2, 5); assertEquals(9, v925.majorVersion()); assertEquals(2, v925.minorVersion()); assertEquals(5, v925.patchVersion()); @@ -375,28 +373,28 @@ public final class SemanticVersionTest { /** * Tests that {@link SemanticVersionNumber#toString} works for simple version * numbers - * + * * @since 2022-02-19 * @since v0.4.0 */ @Test public void testSimpleToString() { - final SemanticVersionNumber v100 = stableVersion(1, 0, 0); + final var v100 = stableVersion(1, 0, 0); assertEquals("1.0.0", v100.toString()); - final SemanticVersionNumber v845a1 = preRelease(8, 4, 5, "alpha", 1); + final var v845a1 = preRelease(8, 4, 5, "alpha", 1); assertEquals("8.4.5-alpha.1", v845a1.toString()); } /** * Tests that simple unstable versions can be created and their parts read - * + * * @since 2022-02-19 * @since v0.4.0 */ @Test public void testSimpleUnstableVersions() { - final SemanticVersionNumber v350a1 = preRelease(3, 5, 0, "alpha", 1); + final var v350a1 = preRelease(3, 5, 0, "alpha", 1); assertEquals(3, v350a1.majorVersion(), "Incorrect major version for v3.5.0a1"); assertEquals(5, v350a1.minorVersion(), diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java index 518c818..8dcd595 100644 --- a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java +++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java @@ -35,48 +35,44 @@ import org.junit.jupiter.api.Test; * @since v0.3.2 */ class UncertainDoubleTest { - /** - * Ensures that the compareTo function behaves correctly. - */ + /** Ensures that the compareTo function behaves correctly. */ @Test final void testCompareTo() { assertTrue(of(2.0, 0.5).compareTo(of(2.0, 0.1)) == 0); assertTrue(of(2.0, 0.5).compareTo(of(1.0, 0.1)) > 0); assertTrue(of(2.0, 0.5).compareTo(of(3.0, 0.1)) < 0); } - - /** - * Tests the ___exact operations - */ + + /** Tests the ___exact operations */ @Test final void testExactOperations() { - final UncertainDouble x = UncertainDouble.of(Math.PI, 0.1); - + final var x = UncertainDouble.of(Math.PI, 0.1); + // slightly different because roundoff errors - final UncertainDouble x1 = UncertainDouble.of(Math.PI + Math.E - Math.E, + final var x1 = UncertainDouble.of(Math.PI + Math.E - Math.E, 0.1); - final UncertainDouble x2 = UncertainDouble.of(Math.PI * Math.E / Math.E, + final var x2 = UncertainDouble.of(Math.PI * Math.E / Math.E, 0.1); - + // get results - final UncertainDouble result1 = x.plusExact(Math.E).minusExact(Math.E); - final UncertainDouble result2 = x.timesExact(Math.E) + final var result1 = x.plusExact(Math.E).minusExact(Math.E); + final var result2 = x.timesExact(Math.E) .dividedByExact(Math.E); - + // test that these operations work & don't change uncertainty assertEquals(x1, result1); assertTrue(x.equivalent(result1)); assertEquals(x2, result2); assertTrue(x.equivalent(result2)); - + // exponents are different assertEquals(Math.pow(Math.PI, Math.E), x.toExponentExact(Math.E).value()); } - + /** * Test for {@link UncertainDouble#fromRoundedString} - * + * * @since 2022-04-18 * @since v0.4.0 */ @@ -84,27 +80,25 @@ class UncertainDoubleTest { final void testFromRoundedString() { assertEquals(of(12345.678, 0.001), fromRoundedString("12345.678")); } - - /** - * Test for {@link UncertainDouble#fromString} - */ + + /** Test for {@link UncertainDouble#fromString} */ @Test final void testFromString() { // valid strings assertEquals(of(2.0, 0.5), fromString("2.0 ± 0.5")); assertEquals(of(2.0, 0.5), fromString("2.0 +- 0.5")); assertEquals(of(2.0, 0.0), fromString("2.0")); - + // invalid strings for (final String s : List.of("2.A", "A", "2.0 ± ", " ± 3.5")) { assertThrows(IllegalArgumentException.class, () -> fromString(s)); } - + // back and forth assertEquals("2.0 ± 0.5", of(2.0, 0.5).toString()); assertEquals("2.0", of(2.0, 0).toString()); } - + @Test final void testHashCode() { assertEquals(of(2.0, 0.5).hashCode(), fromString("2.0 ± 0.5").hashCode()); diff --git a/src/test/java/sevenUnitsGUI/I18nTest.java b/src/test/java/sevenUnitsGUI/I18nTest.java index 78f5da4..2f90d76 100644 --- a/src/test/java/sevenUnitsGUI/I18nTest.java +++ b/src/test/java/sevenUnitsGUI/I18nTest.java @@ -28,68 +28,67 @@ import org.junit.jupiter.params.provider.MethodSource; /** * Tests for the internationalization system. - * + * * @since v1.0.0 */ class I18nTest { + private static final Stream testLocaleSupported() { + return Stream.of("en", "fr"); + } + private static final Stream testLocalization() { - return Stream.of( - Arguments.of("tv.title", "en", "7Units [v]"), + return Stream.of(Arguments.of("tv.title", "en", "7Units [v]"), Arguments.of("tv.title", "fr", "7Unités [v]"), Arguments.of("tv.convert_units.title", "en", "Convert Units"), Arguments.of("tv.convert_units.title", "fr", "Convertir Unités")); } - - private static final Stream testLocaleSupported() { - return Stream.of("en", "fr"); - } /** * Tests that the default locale is supported. - * + * * @since v1.0.0 * @see Presenter#DEFAULT_LOCALE */ @Test void testDefaultLocaleSupported() { - Presenter p = new Presenter(new ViewBot()); + final var p = new Presenter(new ViewBot()); assertNotNull(p.locales.get(Presenter.DEFAULT_LOCALE), "Default locale is not supported."); } - + /** * Ensures that the system supports the provided locale. - * + * * @param localeName locale to test for support - * + * * @since 2025-06-04 * @since v1.0.0 */ @ParameterizedTest @MethodSource void testLocaleSupported(String localeName) { - Presenter p = new Presenter(new ViewBot()); + final var p = new Presenter(new ViewBot()); assertNotNull(p.locales.get(localeName), "Locale \"" + localeName + "\" is not supported."); } /** - * Tests that the system can correctly localize text, - * using the default locales. - * + * Tests that the system can correctly localize text, using the default + * locales. + * * @param key key of text to localize * @param locale locale to use * @param expected expected value of output text - * + * * @since 2025-06-04 * @since v1.0.0 */ @ParameterizedTest @MethodSource void testLocalization(String key, String locale, String expected) { - Presenter p = new Presenter(new ViewBot()); + final var p = new Presenter(new ViewBot()); p.setUserLocale(locale); - String actual = p.getLocalizedText(key); + final var actual = p.getLocalizedText(key); assertEquals(expected, actual); } diff --git a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java index 50b390b..ead5f4a 100644 --- a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java +++ b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java @@ -37,7 +37,7 @@ import sevenUnits.unit.Metric; class PrefixRepetitionTest { /** * Ensures that the complex repetition rule disallows invalid prefix lists. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -57,7 +57,7 @@ class PrefixRepetitionTest { /** * Tests the {@code NO_REPETITION} rule. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -71,7 +71,7 @@ class PrefixRepetitionTest { /** * Tests the {@code NO_RESTRICTION} rule. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -85,7 +85,7 @@ class PrefixRepetitionTest { /** * Ensures that the complex repetition rule allows valid prefix lists. - * + * * @since 2022-07-17 * @since v0.4.0 */ diff --git a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java index b605d05..00dd960 100644 --- a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java +++ b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java @@ -40,9 +40,7 @@ import sevenUnits.unit.Metric; * @since v0.4.0 */ class PrefixSearchTest { - /** - * A method that creates duplicate copies of the common prefix rule. - */ + /** A method that creates duplicate copies of the common prefix rule. */ private static final PrefixSearchRule getCommonRuleCopy() { return getCoherentOnlyRule(Set.of(Metric.KILO, Metric.MILLI)); } @@ -110,7 +108,7 @@ class PrefixSearchTest { /** * Tests prefix searching for a non-coherent unit and * {@link PrefixSearchRule#COMMON_PREFIXES}. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -125,7 +123,7 @@ class PrefixSearchTest { /** * Tests that {@link PrefixSearchRule#NO_PREFIXES} returns the original unit. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -151,10 +149,10 @@ class PrefixSearchTest { */ @Test final void testToString() { - final String toString = COMMON_PREFIXES.toString(); - final String valid1 = "Apply the following prefixes: [kilo (\u00D7 1000.0), milli (\u00D7 0.001)]"; - final String valid2 = "Apply the following prefixes: [milli (\u00D7 0.001), kilo (\u00D7 1000.0)]"; - + final var toString = COMMON_PREFIXES.toString(); + final var valid1 = "Apply the following prefixes: [kilo (\u00D7 1000.0), milli (\u00D7 0.001)]"; + final var valid2 = "Apply the following prefixes: [milli (\u00D7 0.001), kilo (\u00D7 1000.0)]"; + assertTrue(valid1.equals(toString) || valid2.equals(toString), "COMMON_PREFIXES.toString invalid (was \"" + toString + "\")."); } diff --git a/src/test/java/sevenUnitsGUI/RoundingTest.java b/src/test/java/sevenUnitsGUI/RoundingTest.java index e6453f2..589b8d0 100644 --- a/src/test/java/sevenUnitsGUI/RoundingTest.java +++ b/src/test/java/sevenUnitsGUI/RoundingTest.java @@ -137,7 +137,7 @@ class RoundingTest { /** * Tests that the rounding methods' equals() methods work. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -161,7 +161,7 @@ class RoundingTest { // test that FixedDecimals is never equal to FixedPrecision // this unlikely argument is the test - the equals should return false! @SuppressWarnings("unlikely-arg-type") - final boolean differentRulesEqual = Objects.equals(fixedDecimals(4), + final var differentRulesEqual = Objects.equals(fixedDecimals(4), fixedPrecision(4)); assertFalse(differentRulesEqual, "fixedDecimals(4) == fixedPrecision(4)"); } @@ -226,7 +226,7 @@ class RoundingTest { /** * Tests that {@link StandardDisplayRules#getStandardRule} gets rounding * rules as intended. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -244,7 +244,7 @@ class RoundingTest { /** * Tests that the rounding methods' equals() methods work. - * + * * @since 2022-07-17 * @since v0.4.0 */ @@ -258,7 +258,7 @@ class RoundingTest { /** * Tests that the {@code toString()} methods of the three rounding rule * classes work correctly. - * + * * @since 2022-07-17 * @since v0.4.0 */ diff --git a/src/test/java/sevenUnitsGUI/TabbedViewTest.java b/src/test/java/sevenUnitsGUI/TabbedViewTest.java index 3716673..b32579c 100644 --- a/src/test/java/sevenUnitsGUI/TabbedViewTest.java +++ b/src/test/java/sevenUnitsGUI/TabbedViewTest.java @@ -33,67 +33,67 @@ import org.junit.jupiter.api.Timeout; class TabbedViewTest { /** * @return a view with all settings set to standard values - * + * * @since 2022-07-17 * @since v0.4.0 */ private static final TabbedView setupView() { final var view = new TabbedView(); final var presenter = view.getPresenter(); - + presenter.setNumberDisplayRule(StandardDisplayRules.uncertaintyBased()); presenter.setPrefixRepetitionRule( DefaultPrefixRepetitionRule.NO_RESTRICTION); presenter.setSearchRule(PrefixSearchRule.COMMON_PREFIXES); presenter.setOneWayConversionEnabled(false); presenter.setShowDuplicates(true); - + return view; } - + /** * Simulates an expression conversion operation, and ensures it works * properly. - * + * * @since 2022-07-17 * @since v0.4.0 */ @Test void testExpressionConversion() { final var view = setupView(); - + // prepare for unit conversion view.masterPane.setSelectedIndex(1); view.fromEntry.setText("250.0 inch"); view.toEntry.setText("metre"); - + view.convertExpressionButton.doClick(); - + // check result of conversion assertEquals("250.0 inch = 6.350 metre", view.expressionOutput.getText()); } - + /** * Simulates a unit conversion operation, and ensures it works properly. - * + * * @since 2022-07-17 * @since v0.4.0 */ @Test void testUnitConversion() { final var view = setupView(); - + // prepare for unit conversion view.masterPane.setSelectedIndex(0); view.dimensionSelector.setSelectedItem("Length"); view.fromSearch.getSearchList().setSelectedValue("inch", true); view.toSearch.getSearchList().setSelectedValue("metre", true); view.valueInput.setText("250.0"); - + view.convertUnitButton.doClick(); - + // check result of conversion assertEquals("250.0 inch = 6.350 metre", view.unitOutput.getText()); } - + } -- cgit v1.2.3 From bccb5b5e3452421c81c1fb58f83391ba6584807c Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 15 Jun 2025 19:37:19 -0500 Subject: Bump version number to 1.0.0 --- README.org | 4 ++-- src/main/java/sevenUnits/ProgramInfo.java | 5 ++--- src/main/java/sevenUnits/unit/LinearUnit.java | 6 ++---- src/main/java/sevenUnits/unit/LinearUnitValue.java | 3 +-- src/main/java/sevenUnits/unit/UnitDatabase.java | 13 +++++-------- .../sevenUnits/utils/SemanticVersionNumber.java | 5 +++-- src/main/java/sevenUnits/utils/UncertainDouble.java | 8 ++++---- .../sevenUnitsGUI/DefaultPrefixRepetitionRule.java | 3 ++- src/main/java/sevenUnitsGUI/Presenter.java | 3 +-- src/main/java/sevenUnitsGUI/SearchBoxList.java | 4 ++-- src/test/java/sevenUnits/unit/UnitTest.java | 9 +++------ .../utils/ConditionalExistenceCollectionsTest.java | 3 +-- .../java/sevenUnits/utils/SemanticVersionTest.java | 21 +++++++-------------- .../java/sevenUnits/utils/UncertainDoubleTest.java | 9 +++------ 14 files changed, 38 insertions(+), 58 deletions(-) diff --git a/README.org b/README.org index ff46ae2..2e3f227 100644 --- a/README.org +++ b/README.org @@ -1,5 +1,5 @@ -* 7Units Version 1.0.0-beta.2 -(this project uses Semantic Versioning) +* 7Units Version 1.0.0 +This project uses Semantic Versioning, and its public API is all public code in ~src/main/java~. [[http://unmaintained.tech/][http://unmaintained.tech/badge.svg]] ** What is it? diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index 5ed1309..e74a99b 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -25,10 +25,9 @@ import sevenUnits.utils.SemanticVersionNumber; * @since v0.3.1 */ public final class ProgramInfo { - - /** The version number (1.0.0-beta.2) */ + /** The version number (1.0.0) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber - .preRelease(1, 0, 0, "beta", 2); + .stableVersion(1, 0, 0); private ProgramInfo() { // this class is only for static variables, you shouldn't be able to diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 22105b6..85f6dd9 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -220,8 +220,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(divisor, "other must not be null"); // divide the units - final var base = this.getBase() - .dividedBy(divisor.getBase()); + final var base = this.getBase().dividedBy(divisor.getBase()); return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor()); } @@ -382,8 +381,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(multiplier, "other must not be null"); // multiply the units - final var base = this.getBase() - .times(multiplier.getBase()); + final var base = this.getBase().times(multiplier.getBase()); return valueOf(base, this.getConversionFactor() * multiplier.getConversionFactor()); } diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index 9a99e00..ce60e3b 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -150,8 +150,7 @@ public final class LinearUnitValue { remaining = remaining.minus(value); } - final var lastValue = remaining - .convertTo(others.get(others.size() - 1)); + final var lastValue = remaining.convertTo(others.get(others.size() - 1)); values.add(lastValue); return values; } diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 05e9cc9..36c225f 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1229,12 +1229,10 @@ public final class UnitDatabase { private final ExpressionParser prefixExpressionParser = new ExpressionParser.Builder<>( this::getPrefix).addBinaryOperator("+", UnitPrefix::plus, 0) .addBinaryOperator("-", UnitPrefix::minus, 0) - .addBinaryOperator("*", UnitPrefix::times, 1) - .addSpaceFunction("*") + .addBinaryOperator("*", UnitPrefix::times, 1).addSpaceFunction("*") .addBinaryOperator("/", UnitPrefix::dividedBy, 1) - .addBinaryOperator("|", UnitPrefix::dividedBy, 3) - .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), - 2) + .addBinaryOperator("|", UnitPrefix::dividedBy, 3).addBinaryOperator( + "^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), 2) .build(); /** @@ -1624,9 +1622,8 @@ public final class UnitDatabase { if (unit instanceof LinearUnit) return (LinearUnit) unit; - else - throw new IllegalArgumentException( - String.format("%s is not a linear unit.", name)); + throw new IllegalArgumentException( + String.format("%s is not a linear unit.", name)); } /** diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index c081a25..4bb7ce5 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -332,7 +332,7 @@ public final class SemanticVersionNumber if (aNumber < bNumber) return -1; - else if (aNumber > bNumber) + if (aNumber > bNumber) return 1; } else if (NUMERIC_IDENTIFER.matcher(bElement).matches()) // aElement is not a number but bElement is @@ -602,7 +602,8 @@ public final class SemanticVersionNumber return false; } else if (!this.buildMetadata.equals(other.buildMetadata)) return false; - if ((this.major != other.major) || (this.minor != other.minor) || (this.patch != other.patch)) + if ((this.major != other.major) || (this.minor != other.minor) + || (this.patch != other.patch)) return false; if (this.preReleaseIdentifiers == null) { if (other.preReleaseIdentifiers != null) diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java index ecee586..24ada20 100644 --- a/src/main/java/sevenUnits/utils/UncertainDouble.java +++ b/src/main/java/sevenUnits/utils/UncertainDouble.java @@ -207,7 +207,8 @@ public final class UncertainDouble implements Comparable { if (!(obj instanceof UncertainDouble)) return false; final var other = (UncertainDouble) obj; - if ((Double.compare(this.value, other.value) != 0) || (Double.compare(this.uncertainty, other.uncertainty) != 0)) + if ((Double.compare(this.value, other.value) != 0) + || (Double.compare(this.uncertainty, other.uncertainty) != 0)) return false; return true; } @@ -470,10 +471,9 @@ public final class UncertainDouble implements Comparable { final var bigUncertainty = BigDecimal.valueOf(this.uncertainty); final var displayScale = this.getDisplayScale(); - final var roundedUncertainty = bigUncertainty - .setScale(displayScale, roundingMode); - final var roundedValue = bigValue.setScale(displayScale, + final var roundedUncertainty = bigUncertainty.setScale(displayScale, roundingMode); + final var roundedValue = bigValue.setScale(displayScale, roundingMode); valueString = roundedValue.toString(); uncertaintyString = roundedUncertainty.toString(); diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java index a441911..0e38c67 100644 --- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java +++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java @@ -74,7 +74,8 @@ public enum DefaultPrefixRepetitionRule implements Predicate> { for (final UnitPrefix prefix : prefixes) { // check that the current prefix is metric and appropriately // magnifying/reducing - if (!Metric.DECIMAL_PREFIXES.contains(prefix) || (magnifying != prefix.getMultiplier() > 1)) + if (!Metric.DECIMAL_PREFIXES.contains(prefix) + || (magnifying != prefix.getMultiplier() > 1)) return false; // check if the current prefix is correct diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 7fd979a..d258e1f 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -766,8 +766,7 @@ public final class Presenter { } } if (LOCAL_LOCALES.contains(this.userLocale)) { - final var filename = String.format("/about/%s.txt", - this.userLocale); + final var filename = String.format("/about/%s.txt", this.userLocale); return this.formatAboutText( Presenter.getLinesFromResource(filename).stream()); } diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java index bddce04..96f71de 100644 --- a/src/main/java/sevenUnitsGUI/SearchBoxList.java +++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java @@ -237,7 +237,7 @@ final class SearchBoxList extends JPanel { public void reapplyFilter() { final var searchText = this.searchBoxEmpty ? "" : this.searchBox.getText(); - final var comparator = new FilterComparator(searchText, + final var comparator = new FilterComparator<>(searchText, this.defaultOrdering, this.caseSensitive); final var searchFilter = this.getSearchFilter(searchText); @@ -300,7 +300,7 @@ final class SearchBoxList extends JPanel { } final var searchText = this.searchBoxEmpty ? "" : this.searchBox.getText(); - final var comparator = new FilterComparator(searchText, + final var comparator = new FilterComparator<>(searchText, this.defaultOrdering, this.caseSensitive); final var searchFilter = this.getSearchFilter(searchText); diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index 7ae550f..fb21723 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -60,10 +60,8 @@ class UnitTest { // test with LinearUnitValue final var value1 = LinearUnitValue.getExact(Metric.METRE, 15); final var value2 = LinearUnitValue.getExact(foot, 120); - final var value3 = LinearUnitValue.getExact(Metric.METRE, - 0.5); - final var value4 = LinearUnitValue.getExact(Metric.KILOGRAM, - 60); + final var value3 = LinearUnitValue.getExact(Metric.METRE, 0.5); + final var value4 = LinearUnitValue.getExact(Metric.KILOGRAM, 60); // make sure addition is done correctly assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001); @@ -151,8 +149,7 @@ class UnitTest { @Test public void testPrefixes() { - final var generatedKilometre = Metric.METRE - .withPrefix(Metric.KILO); + final var generatedKilometre = Metric.METRE.withPrefix(Metric.KILO); final var actualKilometre = Metric.METRE.times(1000); assertEquals(generatedKilometre, actualKilometre); diff --git a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java index f203fad..8711847 100644 --- a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java +++ b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java @@ -120,8 +120,7 @@ class ConditionalExistenceCollectionsTest { /** Test method for the ConditionalExistenceCollection's iterator. */ @Test void testIterator() { - final var testIterator = this - .getTestIterator(); + final var testIterator = this.getTestIterator(); assertTrue(testIterator.hasNext); assertTrue(testIterator.hasNext()); diff --git a/src/test/java/sevenUnits/utils/SemanticVersionTest.java b/src/test/java/sevenUnits/utils/SemanticVersionTest.java index 047f0b5..5b74812 100644 --- a/src/test/java/sevenUnits/utils/SemanticVersionTest.java +++ b/src/test/java/sevenUnits/utils/SemanticVersionTest.java @@ -72,14 +72,12 @@ public final class SemanticVersionTest { */ @Test public void testComplexToString() { - final var v1 = builder(1, 2, 3).preRelease(1, 2, 3) - .build(); + final var v1 = builder(1, 2, 3).preRelease(1, 2, 3).build(); assertEquals("1.2.3-1.2.3", v1.toString()); final var v2 = builder(4, 5, 6).preRelease("abc", 123) .buildMetadata("2022-02-19").build(); assertEquals("4.5.6-abc.123+2022-02-19", v2.toString()); - final var v3 = builder(1, 0, 0) - .preRelease("x-y-z", "--").build(); + final var v3 = builder(1, 0, 0).preRelease("x-y-z", "--").build(); assertEquals("1.0.0-x-y-z.--", v3.toString()); } @@ -91,8 +89,7 @@ public final class SemanticVersionTest { */ @Test public void testComplexVersions() { - final var v1 = builder(1, 2, 3).preRelease(1, 2, 3) - .build(); + final var v1 = builder(1, 2, 3).preRelease(1, 2, 3).build(); assertEquals(1, v1.majorVersion()); assertEquals(2, v1.minorVersion()); assertEquals(3, v1.patchVersion()); @@ -107,8 +104,7 @@ public final class SemanticVersionTest { assertEquals(List.of("abc", "123"), v2.preReleaseIdentifiers()); assertEquals(List.of("2022-02-19"), v2.buildMetadata()); - final var v3 = builder(1, 0, 0) - .preRelease("x-y-z", "--").build(); + final var v3 = builder(1, 0, 0).preRelease("x-y-z", "--").build(); assertEquals(1, v3.majorVersion()); assertEquals(0, v3.minorVersion()); assertEquals(0, v3.patchVersion()); @@ -298,13 +294,10 @@ public final class SemanticVersionTest { */ @Test public void testOrder() { - final var v100a = builder(1, 0, 0).preRelease("alpha") - .build(); // 1.0.0-alpha + final var v100a = builder(1, 0, 0).preRelease("alpha").build(); // 1.0.0-alpha final var v100a1 = preRelease(1, 0, 0, "alpha", 1); // 1.0.0-alpha.1 - final var v100ab = builder(1, 0, 0) - .preRelease("alpha", "beta").build(); // 1.0.0-alpha.beta - final var v100b = builder(1, 0, 0).preRelease("beta") - .build(); // 1.0.0-alpha + final var v100ab = builder(1, 0, 0).preRelease("alpha", "beta").build(); // 1.0.0-alpha.beta + final var v100b = builder(1, 0, 0).preRelease("beta").build(); // 1.0.0-alpha final var v100b2 = preRelease(1, 0, 0, "beta", 2); // 1.0.0-beta.2 final var v100b11 = preRelease(1, 0, 0, "beta", 11); // 1.0.0-beta.11 final var v100rc1 = preRelease(1, 0, 0, "rc", 1); // 1.0.0-rc.1 diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java index 8dcd595..733a308 100644 --- a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java +++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java @@ -49,15 +49,12 @@ class UncertainDoubleTest { final var x = UncertainDouble.of(Math.PI, 0.1); // slightly different because roundoff errors - final var x1 = UncertainDouble.of(Math.PI + Math.E - Math.E, - 0.1); - final var x2 = UncertainDouble.of(Math.PI * Math.E / Math.E, - 0.1); + final var x1 = UncertainDouble.of(Math.PI + Math.E - Math.E, 0.1); + final var x2 = UncertainDouble.of(Math.PI * Math.E / Math.E, 0.1); // get results final var result1 = x.plusExact(Math.E).minusExact(Math.E); - final var result2 = x.timesExact(Math.E) - .dividedByExact(Math.E); + final var result2 = x.timesExact(Math.E).dividedByExact(Math.E); // test that these operations work & don't change uncertainty assertEquals(x1, result1); -- cgit v1.2.3