From 07c86e02be29aa3d3d878adce62c5c0a9a458e47 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 26 Feb 2022 09:53:24 -0500 Subject: Implemented unit conversion, with a few problems TabbedView now displays its units, but with their toString method which shows their definition in addition to their name --- src/main/java/sevenUnits/utils/NameSymbol.java | 299 +++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 src/main/java/sevenUnits/utils/NameSymbol.java (limited to 'src/main/java/sevenUnits/utils/NameSymbol.java') diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java new file mode 100644 index 0000000..aea274b --- /dev/null +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -0,0 +1,299 @@ +/** + * Copyright (C) 2019 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package 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 + */ +public final class NameSymbol { + public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), + Optional.empty(), new HashSet<>()); + + /** + * Creates a {@code NameSymbol}, ensuring that if primaryName is null and + * otherNames is not empty, one name is moved from otherNames to primaryName + * + * Ensure that otherNames is a copy of the inputted argument. + */ + private static final 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(); + assert it.hasNext(); + primaryName = Optional.of(it.next()); + otherNames.remove(primaryName.get()); + } else { + primaryName = Optional.ofNullable(name); + } + + return new NameSymbol(primaryName, Optional.ofNullable(symbol), + otherNames); + } + + /** + * Gets a {@code NameSymbol} with a primary name, a symbol and no other + * names. + * + * @param name name to use + * @param symbol symbol to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if name or symbol is null + */ + public static final NameSymbol of(final String name, final String symbol) { + return new NameSymbol(Optional.of(name), Optional.of(symbol), + new HashSet<>()); + } + + /** + * 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 + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if any argument is null + */ + public static final 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, + "otherNames must not be null."))); + } + + /** + * 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 + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if any argument is null + */ + public static final NameSymbol of(final String name, final String symbol, + final String... otherNames) { + return new NameSymbol(Optional.of(name), Optional.of(symbol), + new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, + "otherNames must not be null.")))); + } + + /** + * Gets a {@code NameSymbol} with a primary name, no symbol, and no other + * names. + * + * @param name name to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if name is null + */ + public static final NameSymbol ofName(final String name) { + return new NameSymbol(Optional.of(name), Optional.empty(), + new HashSet<>()); + } + + /** + * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. + *

+ * If any argument is null, this static factory replaces it with an empty + * Optional or empty Set. + *

+ * 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 + * @return NameSymbol instance + * @since 2019-11-26 + */ + public static final NameSymbol ofNullable(final String name, + final String symbol, final Set otherNames) { + return NameSymbol.create(name, symbol, + otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); + } + + /** + * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. + *

+ * If any argument is null, this static factory replaces it with an empty + * Optional or empty Set. + *

+ * 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 + * @return NameSymbol instance + * @since 2019-11-26 + */ + public static final NameSymbol ofNullable(final String name, + final String symbol, final String... otherNames) { + return create(name, symbol, otherNames == null ? new HashSet<>() + : new HashSet<>(Arrays.asList(otherNames))); + } + + /** + * Gets a {@code NameSymbol} with a symbol and no names. + * + * @param symbol symbol to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if symbol is null + */ + public static final NameSymbol ofSymbol(final String symbol) { + return new NameSymbol(Optional.empty(), Optional.of(symbol), + new HashSet<>()); + } + + private final Optional primaryName; + private final Optional symbol; + + private final Set otherNames; + + /** + * 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 + * of the argument + * @since 2019-10-21 + */ + private 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 (this.primaryName.isEmpty()) { + assert this.otherNames.isEmpty(); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof NameSymbol)) + return false; + final NameSymbol other = (NameSymbol) obj; + if (this.otherNames == null) { + if (other.otherNames != null) + return false; + } else if (!this.otherNames.equals(other.otherNames)) + return false; + if (this.primaryName == null) { + if (other.primaryName != null) + return false; + } else if (!this.primaryName.equals(other.primaryName)) + return false; + if (this.symbol == null) { + if (other.symbol != null) + return false; + } else if (!this.symbol.equals(other.symbol)) + return false; + return true; + } + + /** + * @return otherNames + * @since 2019-10-21 + */ + public final Set getOtherNames() { + return this.otherNames; + } + + /** + * @return primaryName + * @since 2019-10-21 + */ + public final Optional getPrimaryName() { + return this.primaryName; + } + + /** + * @return symbol + * @since 2019-10-21 + */ + public final Optional getSymbol() { + return this.symbol; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + (this.otherNames == null ? 0 : this.otherNames.hashCode()); + result = prime * result + + (this.primaryName == null ? 0 : this.primaryName.hashCode()); + result = prime * result + + (this.symbol == null ? 0 : this.symbol.hashCode()); + return result; + } + + /** + * @return true iff this {@code NameSymbol} contains no names or symbols. + */ + public final boolean isEmpty() { + // if primaryName is empty, otherNames must also be empty + return this.primaryName.isEmpty() && this.symbol.isEmpty(); + } + + /** + * @return a short version of this NameSymbol (defaults to symbol instead of + * primary name) + * @since 2022-02-26 + */ + public String shortName() { + return this.symbol.or(this::getPrimaryName).orElse("EMPTY"); + } + + @Override + public String toString() { + if (this.isEmpty()) + return "NameSymbol.EMPTY"; + else if (this.primaryName.isPresent() && this.symbol.isPresent()) + return this.primaryName + " (" + this.symbol + ")"; + else + return this.primaryName.orElseGet(this.symbol::orElseThrow); + } +} \ No newline at end of file -- cgit v1.2.3 From 934213e08e85cc20bd994d0f39567426c21b89eb Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 26 Feb 2022 11:15:04 -0500 Subject: Implemented expression conversion, tests now pass --- CHANGELOG.org | 2 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 11 +++ src/main/java/sevenUnits/utils/NameSymbol.java | 9 -- src/main/java/sevenUnits/utils/Nameable.java | 20 ++++ src/main/java/sevenUnits/utils/ObjectProduct.java | 6 +- src/main/java/sevenUnitsGUI/Presenter.java | 77 ++++++++++----- src/main/java/sevenUnitsGUI/TabbedView.java | 15 +-- .../java/sevenUnitsGUI/UnitConversionView.java | 6 +- src/main/java/sevenUnitsGUI/ViewBot.java | 36 +++++-- src/test/java/sevenUnitsGUI/PresenterTest.java | 104 ++++++++++++--------- 10 files changed, 195 insertions(+), 91 deletions(-) (limited to 'src/main/java/sevenUnits/utils/NameSymbol.java') diff --git a/CHANGELOG.org b/CHANGELOG.org index 3bc46c2..681fead 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -4,7 +4,7 @@ *** Added - Added tests for the GUI - Added an object for the version numbers (SemanticVersionNumber) - - Added some toString methods to NameSymbol + - Added some toString methods to NameSymbol and Nameable *** Changed - Rewrote the GUI code internally using an MVP model to make it easier to maintain and improve - BaseDimension is now Nameable. As a consequence, its name and symbol return Optional instead of String, even though they will always succeed. diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index b029539..bf6ae64 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1477,6 +1477,17 @@ public final class UnitDatabase { } } + /** + * Removes all units, prefixes and dimensions from this database. + * + * @since 2022-02-26 + */ + public void clear() { + this.dimensions.clear(); + this.prefixes.clear(); + this.prefixlessUnits.clear(); + } + /** * Tests if the database has a unit dimension with this name. * diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index aea274b..255e82f 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -278,15 +278,6 @@ public final class NameSymbol { return this.primaryName.isEmpty() && this.symbol.isEmpty(); } - /** - * @return a short version of this NameSymbol (defaults to symbol instead of - * primary name) - * @since 2022-02-26 - */ - public String shortName() { - return this.symbol.or(this::getPrimaryName).orElse("EMPTY"); - } - @Override public String toString() { if (this.isEmpty()) diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index 3cfc05a..e469d04 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -26,6 +26,16 @@ import java.util.Set; * @since 2020-09-07 */ 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 + */ + default String getName() { + final NameSymbol ns = this.getNameSymbol(); + return ns.getPrimaryName().or(ns::getSymbol).orElse("Unnamed"); + } + /** * @return a {@code NameSymbol} that contains this object's primary name, * symbol and other names @@ -49,6 +59,16 @@ public interface Nameable { return this.getNameSymbol().getPrimaryName(); } + /** + * @return a short name for the object - if there's a symbol, it's that, + * otherwise the symbol, otherwise "Unnamed" + * @since 2022-02-26 + */ + default String getShortName() { + final NameSymbol ns = this.getNameSymbol(); + return ns.getSymbol().or(ns::getPrimaryName).orElse("Unnamed"); + } + /** * @return short symbol representing object * @since 2020-09-07 diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 926ce10..830f9d7 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -244,9 +244,9 @@ public class ObjectProduct { */ @Override public String toString() { - return this.toString(o -> o instanceof Nameable - ? ((Nameable) o).getNameSymbol().shortName() - : o.toString()); + return this + .toString(o -> o instanceof Nameable ? ((Nameable) o).getShortName() + : o.toString()); } /** diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index be02364..57d353d 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.OptionalDouble; import java.util.Scanner; @@ -34,6 +35,7 @@ import java.util.stream.Collectors; import sevenUnits.ProgramInfo; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.BritishImperial; +import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; @@ -170,7 +172,7 @@ public final class Presenter { /** * The database that this presenter communicates with (effectively the model) */ - private final UnitDatabase database; + final UnitDatabase database; /** * The rule used for parsing input numbers. Any number-string inputted into @@ -274,7 +276,58 @@ public final class Presenter { * {@link ExpressionConversionView}) * @since 2021-12-15 */ - public void convertExpressions() {} + 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; + } + + // 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; + } + 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; + } + + // convert and show output + if (from.getUnit().canConvertTo(to)) { + final double value = from.asUnitValue().convertTo(to).getValue(); + xcview.showExpressionConversionOutput(fromExpression, toExpression, + value); + } else { + this.view.showErrorMessage("Conversion Error", + "Cannot convert between \"" + fromExpression + "\" and \"" + + toExpression + "\"."); + } + + } else + throw new UnsupportedOperationException( + "This function can only be called when the view is an ExpressionConversionView"); + } /** * Converts from the view's input unit to its output unit. Displays an error @@ -326,8 +379,7 @@ public final class Presenter { // convert! final UnitValue initialValue = UnitValue.of(fromUnit, value); final UnitValue converted = initialValue.convertTo(toUnit); - ucview.showUnitConversionOutput( - String.format("%s = %s", initialValue, converted)); + ucview.showUnitConversionOutput(initialValue, converted); } else throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); @@ -365,23 +417,6 @@ public final class Presenter { */ public void saveSettings() {} - /** - * Returns true if and only if the unit represented by {@code unitName} has - * the dimension represented by {@code dimensionName}. - * - * @param unitName name of unit to test - * @param dimensionName name of dimension to test - * @return whether unit has dimenision - * @since 2019-04-13 - * @since v0.2.0 - */ - boolean unitMatchesDimension(String unitName, String dimensionName) { - final Unit unit = this.database.getUnit(unitName); - final ObjectProduct dimension = this.database - .getDimension(dimensionName); - return unit.getDimension().equals(dimension); - } - void unitNameSelected() {} /** diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index c3a05e2..1d40087 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -23,6 +23,7 @@ import java.awt.GridLayout; import java.awt.event.KeyEvent; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.text.ParseException; import java.util.AbstractSet; import java.util.Collections; import java.util.Iterator; @@ -59,6 +60,7 @@ import sevenUnits.ProgramInfo; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitPrefix; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; @@ -140,7 +142,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** The combo box that selects dimensions */ private final JComboBox> dimensionSelector; /** The panel for inputting values in the dimension-based converter */ - private final JTextField valueInput; + private final JFormattedTextField valueInput; /** The panel for "From" in the dimension-based converter */ private final SearchBoxList fromSearch; /** The panel for "To" in the dimension-based converter */ @@ -543,12 +545,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { @Override public OptionalDouble getInputValue() { - final String text = this.valueInput.getText(); try { - return OptionalDouble.of(Double.parseDouble(text)); - } catch (final NumberFormatException e) { + this.valueInput.commitEdit(); + } catch (final ParseException e) { return OptionalDouble.empty(); } + return OptionalDouble + .of(((Number) this.valueInput.getValue()).doubleValue()); } @Override @@ -604,8 +607,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } @Override - public void showUnitConversionOutput(String outputString) { - this.unitOutput.setText(outputString); + public void showUnitConversionOutput(UnitValue input, UnitValue output) { + this.unitOutput.setText(input + " = " + output); } } diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index e411a44..67d3ddc 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -22,6 +22,7 @@ import java.util.Set; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; @@ -94,8 +95,9 @@ public interface UnitConversionView extends View { /** * Shows the output of a unit conversion. * - * @param outputString string that shows output of conversion + * @param input input unit & value (obtained from this view) + * @param output output unit & value * @since 2021-12-24 */ - void showUnitConversionOutput(String outputString); + void showUnitConversionOutput(UnitValue input, UnitValue output); } diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index bc5302b..43d73bb 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -16,6 +16,7 @@ */ package sevenUnitsGUI; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -25,6 +26,7 @@ import java.util.Set; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; @@ -59,8 +61,12 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { private Set fromUnits; /** The units available in the To selection */ private Set toUnits; + /** Saved input values of all unit conversions */ + private final List unitConversionInputValues; /** Saved output values of all unit conversions */ - private List unitConversionOutputValues; + private final List unitConversionOutputValues; + /** Saved outputs of all unit expressions */ + private final List expressionConversionOutputs; /** * Creates a new {@code ViewBot} with a new presenter. @@ -69,6 +75,10 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { */ public ViewBot() { this.presenter = new Presenter(this); + + this.unitConversionInputValues = new ArrayList<>(); + this.unitConversionOutputValues = new ArrayList<>(); + this.expressionConversionOutputs = new ArrayList<>(); } /** @@ -81,7 +91,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } public List getExpressionConversionOutputs() { - throw new UnsupportedOperationException("Not implemented yet"); + return this.expressionConversionOutputs; } @Override @@ -138,11 +148,19 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { return Collections.unmodifiableSet(this.toUnits); } + /** + * @return the unitConversionInputValues + * @since 2022-02-26 + */ + public List getUnitConversionInputValues() { + return this.unitConversionInputValues; + } + /** * @return the unitConversionOutputValues * @since 2022-02-10 */ - public List getUnitConversionOutputValues() { + public List getUnitConversionOutputValues() { return this.unitConversionOutputValues; } @@ -247,13 +265,17 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { @Override public void showExpressionConversionOutput(String fromExpression, String toExpression, double value) { - System.out.printf("Expression Conversion: %s = %d * (%s)%n", - fromExpression, value, toExpression); + final String output = String.format("%s = %s %s", fromExpression, value, + toExpression); + this.expressionConversionOutputs.add(output); + System.out.println("Expression Conversion: " + output); } @Override - public void showUnitConversionOutput(String outputString) { - System.out.println("Unit conversion: " + outputString); + public void showUnitConversionOutput(UnitValue input, UnitValue output) { + this.unitConversionInputValues.add(input); + this.unitConversionOutputValues.add(output); + System.out.println("Unit conversion: " + input + " = " + output); } @Override diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index ff1450b..deb16d7 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -19,16 +19,19 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.OptionalDouble; import java.util.Set; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.NamedObjectProduct; @@ -38,50 +41,19 @@ import sevenUnits.utils.NamedObjectProduct; * @since 2022-02-10 */ public final class PresenterTest { + private static final List unitNames( + Collection units) { + return units.stream().map(Unit::getShortName) + .collect(Collectors.toList()); + } + Set testUnits = Set.of(Metric.METRE, Metric.KILOMETRE, Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR); + Set> testDimensions = Set.of( Metric.Dimensions.LENGTH.withName(NameSymbol.ofName("Length")), Metric.Dimensions.VELOCITY.withName(NameSymbol.ofName("Velocity"))); - /** - * Test for {@link Presenter#updateView()} - * - * @since 2022-02-12 - */ - @Test - void testUpdateView() { - // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); - - viewBot.setFromUnits(this.testUnits); - viewBot.setToUnits(this.testUnits); - viewBot.setDimensions(this.testDimensions); - viewBot.setSelectedDimension( - Optional.of(this.testDimensions.iterator().next())); - - // filter to length units only, then get the filtered sets of units - presenter.updateView(); - final Set fromUnits = viewBot.getFromUnits(); - final Set toUnits = viewBot.getToUnits(); - - // test that fromUnits/toUnits is [METRE, KILOMETRE] - // HOWEVER I don't care about the order so I'm testing it this way - assertEquals(2, fromUnits.size(), - "Invalid fromUnits (length != 2): " + fromUnits); - assertEquals(2, toUnits.size(), - "Invalid toUnits (length != 2): " + toUnits); - assertTrue(fromUnits.contains(Metric.METRE), - "Invaild fromUnits (METRE missing): " + fromUnits); - assertTrue(toUnits.contains(Metric.METRE), - "Invaild toUnits (METRE missing): " + toUnits); - assertTrue(fromUnits.contains(Metric.KILOMETRE), - "Invaild fromUnits (KILOMETRE missing): " + fromUnits); - assertTrue(toUnits.contains(Metric.KILOMETRE), - "Invaild toUnits (KILOMETRE missing): " + toUnits); - } - /** * Test method for {@link Presenter#convertExpressions} * @@ -129,10 +101,58 @@ public final class PresenterTest { * here (that's for the backend tests), I'm just testing that it correctly * calls the unit conversion system */ - final String expected = String - .valueOf(Metric.METRE.convertTo(Metric.KILOMETRE, 10000.0)); + final UnitValue expectedInput = UnitValue.of(Metric.METRE, 10000.0); + final UnitValue expectedOutput = expectedInput + .convertTo(Metric.KILOMETRE); + + final List inputs = viewBot.getUnitConversionInputValues(); + final List outputs = viewBot.getUnitConversionOutputValues(); + assertEquals(expectedInput, inputs.get(inputs.size() - 1)); + assertEquals(expectedOutput, outputs.get(outputs.size() - 1)); + } + + /** + * Test for {@link Presenter#updateView()} + * + * @since 2022-02-12 + */ + @Test + void testUpdateView() { + // setup + final ViewBot viewBot = new ViewBot(); + final Presenter presenter = new Presenter(viewBot); + + // override default database units + presenter.database.clear(); + for (final Unit unit : this.testUnits) { + presenter.database.addUnit(unit.getPrimaryName().orElseThrow(), unit); + } + + // set from and to units + viewBot.setFromUnits(this.testUnits); + viewBot.setToUnits(this.testUnits); + viewBot.setDimensions(this.testDimensions); + viewBot.setSelectedDimension( + Optional.of(this.testDimensions.iterator().next())); + + // filter to length units only, then get the filtered sets of units + presenter.updateView(); + final Set fromUnits = viewBot.getFromUnits(); + final Set toUnits = viewBot.getToUnits(); - final List outputs = viewBot.getUnitConversionOutputValues(); - assertEquals(expected, outputs.get(outputs.size() - 1)); + // test that fromUnits/toUnits is [METRE, KILOMETRE] + // HOWEVER I don't care about the order so I'm testing it this way + assertEquals(2, fromUnits.size(), + "Invalid fromUnits (length != 2): " + unitNames(fromUnits)); + assertEquals(2, toUnits.size(), + "Invalid toUnits (length != 2): " + unitNames(toUnits)); + assertTrue(fromUnits.contains(Metric.METRE), + "Invaild fromUnits (METRE missing): " + unitNames(fromUnits)); + assertTrue(toUnits.contains(Metric.METRE), + "Invaild toUnits (METRE missing): " + unitNames(toUnits)); + assertTrue(fromUnits.contains(Metric.KILOMETRE), + "Invaild fromUnits (KILOMETRE missing): " + unitNames(fromUnits)); + assertTrue(toUnits.contains(Metric.KILOMETRE), + "Invaild toUnits (KILOMETRE missing): " + unitNames(toUnits)); } } -- cgit v1.2.3 From 91f87da88f98de996e167f0ff6809356f6d57e11 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 10 Mar 2022 15:14:33 -0500 Subject: Named the metric dimensions & fixed NameSymbol.toString --- src/main/java/sevenUnits/unit/Metric.java | 153 ++++++++++++++++--------- src/main/java/sevenUnits/utils/NameSymbol.java | 3 +- src/main/resources/dimensionfile.txt | 8 +- src/test/java/sevenUnitsGUI/PresenterTest.java | 29 +++-- 4 files changed, 117 insertions(+), 76 deletions(-) (limited to 'src/main/java/sevenUnits/utils/NameSymbol.java') diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 78e3769..7ede085 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -19,6 +19,7 @@ package sevenUnits.unit; import java.util.Set; import sevenUnits.utils.NameSymbol; +import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; /** @@ -114,32 +115,39 @@ public final class Metric { public static final class Dimensions { public static final ObjectProduct EMPTY = ObjectProduct .empty(); - public static final ObjectProduct LENGTH = ObjectProduct - .oneOf(BaseDimensions.LENGTH); - public static final ObjectProduct MASS = ObjectProduct - .oneOf(BaseDimensions.MASS); - public static final ObjectProduct TIME = ObjectProduct - .oneOf(BaseDimensions.TIME); - public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct - .oneOf(BaseDimensions.ELECTRIC_CURRENT); - public static final ObjectProduct TEMPERATURE = ObjectProduct - .oneOf(BaseDimensions.TEMPERATURE); - public static final ObjectProduct QUANTITY = ObjectProduct - .oneOf(BaseDimensions.QUANTITY); - public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct - .oneOf(BaseDimensions.LUMINOUS_INTENSITY); - public static final ObjectProduct INFORMATION = ObjectProduct - .oneOf(BaseDimensions.INFORMATION); - public static final ObjectProduct CURRENCY = ObjectProduct - .oneOf(BaseDimensions.CURRENCY); + public static final NamedObjectProduct LENGTH = ObjectProduct + .oneOf(BaseDimensions.LENGTH) + .withName(NameSymbol.of("Length", "L")); + public static final NamedObjectProduct MASS = ObjectProduct + .oneOf(BaseDimensions.MASS).withName(NameSymbol.of("Mass", "M")); + public static final NamedObjectProduct TIME = ObjectProduct + .oneOf(BaseDimensions.TIME).withName(NameSymbol.of("Time", "T")); + public static final NamedObjectProduct ELECTRIC_CURRENT = ObjectProduct + .oneOf(BaseDimensions.ELECTRIC_CURRENT) + .withName(NameSymbol.of("Current", "I")); + public static final NamedObjectProduct TEMPERATURE = ObjectProduct + .oneOf(BaseDimensions.TEMPERATURE) + .withName(NameSymbol.of("Temperature", "\u0398")); + public static final NamedObjectProduct QUANTITY = ObjectProduct + .oneOf(BaseDimensions.QUANTITY) + .withName(NameSymbol.of("Quantity", "N")); + public static final NamedObjectProduct LUMINOUS_INTENSITY = ObjectProduct + .oneOf(BaseDimensions.LUMINOUS_INTENSITY) + .withName(NameSymbol.of("Luminous Intensity", "J")); + public static final NamedObjectProduct INFORMATION = ObjectProduct + .oneOf(BaseDimensions.INFORMATION) + .withName(NameSymbol.ofName("Information")); + public static final NamedObjectProduct CURRENCY = ObjectProduct + .oneOf(BaseDimensions.CURRENCY) + .withName(NameSymbol.ofName("Currency")); // derived dimensions without named SI units public static final ObjectProduct AREA = LENGTH .times(LENGTH); public static final ObjectProduct VOLUME = AREA .times(LENGTH); - public static final ObjectProduct VELOCITY = LENGTH - .dividedBy(TIME); + public static final NamedObjectProduct VELOCITY = LENGTH + .dividedBy(TIME).withName(NameSymbol.ofName("Velocity")); public static final ObjectProduct ACCELERATION = VELOCITY .dividedBy(TIME); public static final ObjectProduct WAVENUMBER = EMPTY @@ -403,54 +411,89 @@ public final class Metric { .withName(NameSymbol.of("exbi", "Ei")); // a few prefixed units - public static final LinearUnit MICROMETRE = Metric.METRE.withPrefix(Metric.MICRO); - public static final LinearUnit MILLIMETRE = Metric.METRE.withPrefix(Metric.MILLI); - public static final LinearUnit KILOMETRE = Metric.METRE.withPrefix(Metric.KILO); - public static final LinearUnit MEGAMETRE = Metric.METRE.withPrefix(Metric.MEGA); + public static final LinearUnit MICROMETRE = Metric.METRE + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLIMETRE = Metric.METRE + .withPrefix(Metric.MILLI); + public static final LinearUnit KILOMETRE = Metric.METRE + .withPrefix(Metric.KILO); + public static final LinearUnit MEGAMETRE = Metric.METRE + .withPrefix(Metric.MEGA); - public static final LinearUnit MICROLITRE = Metric.LITRE.withPrefix(Metric.MICRO); - public static final LinearUnit MILLILITRE = Metric.LITRE.withPrefix(Metric.MILLI); - public static final LinearUnit KILOLITRE = Metric.LITRE.withPrefix(Metric.KILO); - public static final LinearUnit MEGALITRE = Metric.LITRE.withPrefix(Metric.MEGA); + public static final LinearUnit MICROLITRE = Metric.LITRE + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLILITRE = Metric.LITRE + .withPrefix(Metric.MILLI); + public static final LinearUnit KILOLITRE = Metric.LITRE + .withPrefix(Metric.KILO); + public static final LinearUnit MEGALITRE = Metric.LITRE + .withPrefix(Metric.MEGA); - public static final LinearUnit MICROSECOND = Metric.SECOND.withPrefix(Metric.MICRO); - public static final LinearUnit MILLISECOND = Metric.SECOND.withPrefix(Metric.MILLI); - public static final LinearUnit KILOSECOND = Metric.SECOND.withPrefix(Metric.KILO); - public static final LinearUnit MEGASECOND = Metric.SECOND.withPrefix(Metric.MEGA); + public static final LinearUnit MICROSECOND = Metric.SECOND + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLISECOND = Metric.SECOND + .withPrefix(Metric.MILLI); + public static final LinearUnit KILOSECOND = Metric.SECOND + .withPrefix(Metric.KILO); + public static final LinearUnit MEGASECOND = Metric.SECOND + .withPrefix(Metric.MEGA); - public static final LinearUnit MICROGRAM = Metric.GRAM.withPrefix(Metric.MICRO); - public static final LinearUnit MILLIGRAM = Metric.GRAM.withPrefix(Metric.MILLI); - public static final LinearUnit MEGAGRAM = Metric.GRAM.withPrefix(Metric.MEGA); + public static final LinearUnit MICROGRAM = Metric.GRAM + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLIGRAM = Metric.GRAM + .withPrefix(Metric.MILLI); + public static final LinearUnit MEGAGRAM = Metric.GRAM + .withPrefix(Metric.MEGA); - public static final LinearUnit MICRONEWTON = Metric.NEWTON.withPrefix(Metric.MICRO); - public static final LinearUnit MILLINEWTON = Metric.NEWTON.withPrefix(Metric.MILLI); - public static final LinearUnit KILONEWTON = Metric.NEWTON.withPrefix(Metric.KILO); - public static final LinearUnit MEGANEWTON = Metric.NEWTON.withPrefix(Metric.MEGA); + public static final LinearUnit MICRONEWTON = Metric.NEWTON + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLINEWTON = Metric.NEWTON + .withPrefix(Metric.MILLI); + public static final LinearUnit KILONEWTON = Metric.NEWTON + .withPrefix(Metric.KILO); + public static final LinearUnit MEGANEWTON = Metric.NEWTON + .withPrefix(Metric.MEGA); - public static final LinearUnit MICROJOULE = Metric.JOULE.withPrefix(Metric.MICRO); - public static final LinearUnit MILLIJOULE = Metric.JOULE.withPrefix(Metric.MILLI); - public static final LinearUnit KILOJOULE = Metric.JOULE.withPrefix(Metric.KILO); - public static final LinearUnit MEGAJOULE = Metric.JOULE.withPrefix(Metric.MEGA); + public static final LinearUnit MICROJOULE = Metric.JOULE + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLIJOULE = Metric.JOULE + .withPrefix(Metric.MILLI); + public static final LinearUnit KILOJOULE = Metric.JOULE + .withPrefix(Metric.KILO); + public static final LinearUnit MEGAJOULE = Metric.JOULE + .withPrefix(Metric.MEGA); - public static final LinearUnit MICROWATT = Metric.WATT.withPrefix(Metric.MICRO); - public static final LinearUnit MILLIWATT = Metric.WATT.withPrefix(Metric.MILLI); - public static final LinearUnit KILOWATT = Metric.WATT.withPrefix(Metric.KILO); - public static final LinearUnit MEGAWATT = Metric.WATT.withPrefix(Metric.MEGA); + public static final LinearUnit MICROWATT = Metric.WATT + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLIWATT = Metric.WATT + .withPrefix(Metric.MILLI); + public static final LinearUnit KILOWATT = Metric.WATT + .withPrefix(Metric.KILO); + public static final LinearUnit MEGAWATT = Metric.WATT + .withPrefix(Metric.MEGA); public static final LinearUnit MICROCOULOMB = Metric.COULOMB .withPrefix(Metric.MICRO); public static final LinearUnit MILLICOULOMB = Metric.COULOMB .withPrefix(Metric.MILLI); - public static final LinearUnit KILOCOULOMB = Metric.COULOMB.withPrefix(Metric.KILO); - public static final LinearUnit MEGACOULOMB = Metric.COULOMB.withPrefix(Metric.MEGA); + public static final LinearUnit KILOCOULOMB = Metric.COULOMB + .withPrefix(Metric.KILO); + public static final LinearUnit MEGACOULOMB = Metric.COULOMB + .withPrefix(Metric.MEGA); - public static final LinearUnit MICROAMPERE = Metric.AMPERE.withPrefix(Metric.MICRO); - public static final LinearUnit MILLIAMPERE = Metric.AMPERE.withPrefix(Metric.MILLI); + public static final LinearUnit MICROAMPERE = Metric.AMPERE + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLIAMPERE = Metric.AMPERE + .withPrefix(Metric.MILLI); - public static final LinearUnit MICROVOLT = Metric.VOLT.withPrefix(Metric.MICRO); - public static final LinearUnit MILLIVOLT = Metric.VOLT.withPrefix(Metric.MILLI); - public static final LinearUnit KILOVOLT = Metric.VOLT.withPrefix(Metric.KILO); - public static final LinearUnit MEGAVOLT = Metric.VOLT.withPrefix(Metric.MEGA); + public static final LinearUnit MICROVOLT = Metric.VOLT + .withPrefix(Metric.MICRO); + public static final LinearUnit MILLIVOLT = Metric.VOLT + .withPrefix(Metric.MILLI); + public static final LinearUnit KILOVOLT = Metric.VOLT + .withPrefix(Metric.KILO); + public static final LinearUnit MEGAVOLT = Metric.VOLT + .withPrefix(Metric.MEGA); public static final LinearUnit KILOOHM = Metric.OHM.withPrefix(Metric.KILO); public static final LinearUnit MEGAOHM = Metric.OHM.withPrefix(Metric.MEGA); diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 255e82f..41cf41d 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -283,7 +283,8 @@ public final class NameSymbol { if (this.isEmpty()) return "NameSymbol.EMPTY"; else if (this.primaryName.isPresent() && this.symbol.isPresent()) - return this.primaryName + " (" + this.symbol + ")"; + return this.primaryName.orElseThrow() + " (" + + this.symbol.orElseThrow() + ")"; else return this.primaryName.orElseGet(this.symbol::orElseThrow); } diff --git a/src/main/resources/dimensionfile.txt b/src/main/resources/dimensionfile.txt index 3485de5..a946677 100644 --- a/src/main/resources/dimensionfile.txt +++ b/src/main/resources/dimensionfile.txt @@ -12,7 +12,7 @@ TIME ! TEMPERATURE ! # Derived Dimensions -AREA LENGTH^2 -VOLUME LENGTH^3 -VELOCITY LENGTH / TIME -ENERGY MASS * VELOCITY^2 \ No newline at end of file +Area LENGTH^2 +Volume LENGTH^3 +Velocity LENGTH / TIME +Energy MASS * Velocity^2 \ No newline at end of file diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index deb16d7..82842d8 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -32,7 +32,6 @@ import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitValue; -import sevenUnits.utils.NameSymbol; import sevenUnits.utils.NamedObjectProduct; /** @@ -41,19 +40,18 @@ import sevenUnits.utils.NamedObjectProduct; * @since 2022-02-10 */ public final class PresenterTest { + static final Set testUnits = Set.of(Metric.METRE, Metric.KILOMETRE, + Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR); + + static final Set> testDimensions = Set + .of(Metric.Dimensions.LENGTH, Metric.Dimensions.VELOCITY); + private static final List unitNames( Collection units) { return units.stream().map(Unit::getShortName) .collect(Collectors.toList()); } - Set testUnits = Set.of(Metric.METRE, Metric.KILOMETRE, - Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR); - - Set> testDimensions = Set.of( - Metric.Dimensions.LENGTH.withName(NameSymbol.ofName("Length")), - Metric.Dimensions.VELOCITY.withName(NameSymbol.ofName("Velocity"))); - /** * Test method for {@link Presenter#convertExpressions} * @@ -87,8 +85,8 @@ public final class PresenterTest { final ViewBot viewBot = new ViewBot(); final Presenter presenter = new Presenter(viewBot); - viewBot.setFromUnits(this.testUnits); - viewBot.setToUnits(this.testUnits); + viewBot.setFromUnits(testUnits); + viewBot.setToUnits(testUnits); viewBot.setFromSelection(Optional.of(Metric.METRE)); viewBot.setToSelection(Optional.of(Metric.KILOMETRE)); viewBot.setInputValue(OptionalDouble.of(10000.0)); @@ -124,16 +122,15 @@ public final class PresenterTest { // override default database units presenter.database.clear(); - for (final Unit unit : this.testUnits) { + for (final Unit unit : testUnits) { presenter.database.addUnit(unit.getPrimaryName().orElseThrow(), unit); } // set from and to units - viewBot.setFromUnits(this.testUnits); - viewBot.setToUnits(this.testUnits); - viewBot.setDimensions(this.testDimensions); - viewBot.setSelectedDimension( - Optional.of(this.testDimensions.iterator().next())); + viewBot.setFromUnits(testUnits); + viewBot.setToUnits(testUnits); + viewBot.setDimensions(testDimensions); + viewBot.setSelectedDimension(Optional.of(Metric.Dimensions.LENGTH)); // filter to length units only, then get the filtered sets of units presenter.updateView(); -- cgit v1.2.3 From 4ad68a29f84538d3fb19eec8e0622731f5a5d7c8 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Tue, 12 Apr 2022 15:17:12 -0500 Subject: Removed NamedObjectProduct in favour of the regular ObjectProduct --- src/main/java/sevenUnits/unit/Metric.java | 21 +++++---- src/main/java/sevenUnits/unit/Unit.java | 4 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 12 +++-- src/main/java/sevenUnits/utils/NameSymbol.java | 2 +- .../java/sevenUnits/utils/NamedObjectProduct.java | 51 ---------------------- src/main/java/sevenUnits/utils/ObjectProduct.java | 30 +++++++++++-- src/test/java/sevenUnitsGUI/PresenterTest.java | 4 +- 7 files changed, 46 insertions(+), 78 deletions(-) delete mode 100644 src/main/java/sevenUnits/utils/NamedObjectProduct.java (limited to 'src/main/java/sevenUnits/utils/NameSymbol.java') diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 7ede085..05e82ba 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -19,7 +19,6 @@ package sevenUnits.unit; import java.util.Set; import sevenUnits.utils.NameSymbol; -import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; /** @@ -115,29 +114,29 @@ public final class Metric { public static final class Dimensions { public static final ObjectProduct EMPTY = ObjectProduct .empty(); - public static final NamedObjectProduct LENGTH = ObjectProduct + public static final ObjectProduct LENGTH = ObjectProduct .oneOf(BaseDimensions.LENGTH) .withName(NameSymbol.of("Length", "L")); - public static final NamedObjectProduct MASS = ObjectProduct + public static final ObjectProduct MASS = ObjectProduct .oneOf(BaseDimensions.MASS).withName(NameSymbol.of("Mass", "M")); - public static final NamedObjectProduct TIME = ObjectProduct + public static final ObjectProduct TIME = ObjectProduct .oneOf(BaseDimensions.TIME).withName(NameSymbol.of("Time", "T")); - public static final NamedObjectProduct ELECTRIC_CURRENT = ObjectProduct + public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct .oneOf(BaseDimensions.ELECTRIC_CURRENT) .withName(NameSymbol.of("Current", "I")); - public static final NamedObjectProduct TEMPERATURE = ObjectProduct + public static final ObjectProduct TEMPERATURE = ObjectProduct .oneOf(BaseDimensions.TEMPERATURE) .withName(NameSymbol.of("Temperature", "\u0398")); - public static final NamedObjectProduct QUANTITY = ObjectProduct + public static final ObjectProduct QUANTITY = ObjectProduct .oneOf(BaseDimensions.QUANTITY) .withName(NameSymbol.of("Quantity", "N")); - public static final NamedObjectProduct LUMINOUS_INTENSITY = ObjectProduct + public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct .oneOf(BaseDimensions.LUMINOUS_INTENSITY) .withName(NameSymbol.of("Luminous Intensity", "J")); - public static final NamedObjectProduct INFORMATION = ObjectProduct + public static final ObjectProduct INFORMATION = ObjectProduct .oneOf(BaseDimensions.INFORMATION) .withName(NameSymbol.ofName("Information")); - public static final NamedObjectProduct CURRENCY = ObjectProduct + public static final ObjectProduct CURRENCY = ObjectProduct .oneOf(BaseDimensions.CURRENCY) .withName(NameSymbol.ofName("Currency")); @@ -146,7 +145,7 @@ public final class Metric { .times(LENGTH); public static final ObjectProduct VOLUME = AREA .times(LENGTH); - public static final NamedObjectProduct VELOCITY = LENGTH + public static final ObjectProduct VELOCITY = LENGTH .dividedBy(TIME).withName(NameSymbol.ofName("Velocity")); public static final ObjectProduct ACCELERATION = VELOCITY .dividedBy(TIME); diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 826b59b..14478ba 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -354,8 +354,8 @@ public abstract class Unit implements Nameable { * @since 2022-03-10 */ public String toDefinitionString() { - if (this.unitBase instanceof Nameable) - return "derived from " + ((Nameable) this.unitBase).getName(); + if (!this.unitBase.getNameSymbol().isEmpty()) + return "derived from " + this.unitBase.getName(); else return "derived from " + this.getBase().toString(BaseUnit::getShortName); diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index bf6ae64..5591d7d 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -48,7 +48,6 @@ import sevenUnits.utils.ConditionalExistenceCollections; import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.ExpressionParser; import sevenUnits.utils.NameSymbol; -import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; @@ -1199,7 +1198,7 @@ public final class UnitDatabase { * @since 2019-03-14 * @since v0.2.0 */ - private final Map> dimensions; + private final Map> dimensions; /** * A map mapping strings to units (including prefixes) @@ -1317,11 +1316,10 @@ public final class UnitDatabase { final ObjectProduct dimension) { Objects.requireNonNull(name, "name may not be null"); Objects.requireNonNull(dimension, "dimension may not be null"); - if (dimension instanceof NamedObjectProduct) { - this.dimensions.put(name, - (NamedObjectProduct) dimension); + if (!dimension.getNameSymbol().equals(NameSymbol.EMPTY)) { + this.dimensions.put(name, dimension); } else { - final NamedObjectProduct namedDimension = dimension + final ObjectProduct namedDimension = dimension .withName(NameSymbol.ofName(name)); this.dimensions.put(name, namedDimension); } @@ -1530,7 +1528,7 @@ public final class UnitDatabase { * @since 2019-04-13 * @since v0.2.0 */ - public Map> dimensionMap() { + public Map> dimensionMap() { return Collections.unmodifiableMap(this.dimensions); } diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 41cf41d..955f980 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -38,7 +38,7 @@ 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 a copy of the inputted argument. + * 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) { diff --git a/src/main/java/sevenUnits/utils/NamedObjectProduct.java b/src/main/java/sevenUnits/utils/NamedObjectProduct.java deleted file mode 100644 index 89b2fad..0000000 --- a/src/main/java/sevenUnits/utils/NamedObjectProduct.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (C) 2021 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 java.util.Map; - -/** - * An ObjectProduct with name(s) and/or a symbol. Can be created with the - * {@link ObjectProduct#withName} method. - * - * @author Adrien Hopkins - * @since 2021-12-15 - */ -public class NamedObjectProduct extends ObjectProduct - implements Nameable { - private final NameSymbol nameSymbol; - - NamedObjectProduct(final Map exponents, - final NameSymbol nameSymbol) { - super(exponents); - this.nameSymbol = nameSymbol; - } - - @Override - public NameSymbol getNameSymbol() { - return this.nameSymbol; - } - - public final String toDefinitionString() { - return super.toString(); - } - - @Override - public String toString() { - return this.nameSymbol.toString(); - } -} diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 830f9d7..110bdc1 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -33,7 +33,7 @@ import java.util.function.Function; * @author Adrien Hopkins * @since 2019-10-16 */ -public class ObjectProduct { +public class ObjectProduct implements Nameable { /** * Returns an empty ObjectProduct of a certain type * @@ -83,15 +83,32 @@ public class ObjectProduct { final Map exponents; /** - * Creates the {@code ObjectProduct}. + * 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 */ 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 + */ + ObjectProduct(final Map exponents, NameSymbol nameSymbol) { this.exponents = Collections.unmodifiableMap( ConditionalExistenceCollections.conditionalExistenceMap(exponents, e -> !Integer.valueOf(0).equals(e.getValue()))); + this.nameSymbol = nameSymbol; } /** @@ -170,6 +187,11 @@ public class ObjectProduct { return this.exponents.getOrDefault(dimension, 0); } + @Override + public NameSymbol getNameSymbol() { + return this.nameSymbol; + } + @Override public int hashCode() { return Objects.hash(this.exponents); @@ -288,7 +310,7 @@ public class ObjectProduct { * {@code nameSymbol} * @since 2021-12-15 */ - public NamedObjectProduct withName(NameSymbol nameSymbol) { - return new NamedObjectProduct<>(this.exponents, nameSymbol); + public ObjectProduct withName(NameSymbol nameSymbol) { + return new ObjectProduct<>(this.exponents, nameSymbol); } } diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index dc2fb57..3fe7e47 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -30,7 +30,7 @@ import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitValue; import sevenUnits.utils.Nameable; -import sevenUnits.utils.NamedObjectProduct; +import sevenUnits.utils.ObjectProduct; /** * @author Adrien Hopkins @@ -41,7 +41,7 @@ public final class PresenterTest { static final Set testUnits = Set.of(Metric.METRE, Metric.KILOMETRE, Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR); - static final Set> testDimensions = Set + static final Set> testDimensions = Set .of(Metric.Dimensions.LENGTH, Metric.Dimensions.VELOCITY); private static final Set names(Set units) { -- cgit v1.2.3 From 0aacba9fc8a9140fdf331172ad66afe280d09b5e Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Tue, 19 Apr 2022 16:10:44 -0500 Subject: Implemented prefix settings, saving & loading of settings Also fixed some bugs: - Presenter now has default values for its settings in case they don't load properly - UnitDatabase ensures its units, prefixes and dimensions have all of the names you give it --- src/main/java/sevenUnits/unit/UnitDatabase.java | 29 ++-- src/main/java/sevenUnits/utils/NameSymbol.java | 17 ++ .../sevenUnitsGUI/DefaultPrefixRepetitionRule.java | 95 +++++++++++ src/main/java/sevenUnitsGUI/Presenter.java | 175 ++++++++++++++++++--- src/main/java/sevenUnitsGUI/TabbedView.java | 77 ++++++--- .../java/sevenUnitsGUI/UnitConversionRecord.java | 19 +++ src/main/java/sevenUnitsGUI/View.java | 6 + src/main/java/sevenUnitsGUI/ViewBot.java | 23 +-- src/test/java/sevenUnitsGUI/PresenterTest.java | 25 +-- src/test/resources/test-settings.txt | 4 + 10 files changed, 384 insertions(+), 86 deletions(-) create mode 100644 src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java create mode 100644 src/test/resources/test-settings.txt (limited to 'src/main/java/sevenUnits/utils/NameSymbol.java') diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 71676a1..12b78a7 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1315,13 +1315,9 @@ public final class UnitDatabase { final ObjectProduct dimension) { Objects.requireNonNull(name, "name may not be null"); Objects.requireNonNull(dimension, "dimension may not be null"); - if (!dimension.getNameSymbol().equals(NameSymbol.EMPTY)) { - this.dimensions.put(name, dimension); - } else { - final ObjectProduct namedDimension = dimension - .withName(NameSymbol.ofName(name)); - this.dimensions.put(name, namedDimension); - } + final ObjectProduct namedDimension = dimension + .withName(dimension.getNameSymbol().withExtraName(name)); + this.dimensions.put(name, namedDimension); } /** @@ -1373,7 +1369,7 @@ public final class UnitDatabase { throw e; } - this.addDimension(name, dimension.withName(NameSymbol.ofName(name))); + this.addDimension(name, dimension); } } @@ -1387,8 +1383,11 @@ public final class UnitDatabase { * @since v0.1.0 */ public void addPrefix(final String name, final UnitPrefix prefix) { + Objects.requireNonNull(prefix, "prefix may not be null"); + final var namedPrefix = prefix + .withName(prefix.getNameSymbol().withExtraName(name)); this.prefixes.put(Objects.requireNonNull(name, "name must not be null."), - Objects.requireNonNull(prefix, "prefix must not be null.")); + namedPrefix); } /** @@ -1401,9 +1400,11 @@ public final class UnitDatabase { * @since v0.1.0 */ public void addUnit(final String name, final Unit unit) { + Objects.requireNonNull(unit, "unit may not be null"); + final var namedUnit = unit + .withName(unit.getNameSymbol().withExtraName(name)); this.prefixlessUnits.put( - Objects.requireNonNull(name, "name must not be null."), - Objects.requireNonNull(unit, "unit must not be null.")); + Objects.requireNonNull(name, "name must not be null."), namedUnit); } /** @@ -1458,8 +1459,7 @@ public final class UnitDatabase { throw e; } final String prefixName = name.substring(0, name.length() - 1); - this.addPrefix(prefixName, - prefix.withName(NameSymbol.ofName(prefixName))); + this.addPrefix(prefixName, prefix); } else { // it's a unit, get the unit final Unit unit; @@ -1470,8 +1470,7 @@ public final class UnitDatabase { System.err.printf("Parsing error on line %d:%n", lineCounter); throw e; } - - this.addUnit(name, unit.withName(NameSymbol.ofName(name))); + this.addUnit(name, unit); } } } diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 955f980..7ef2967 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -288,4 +288,21 @@ public final class NameSymbol { else return this.primaryName.orElseGet(this.symbol::orElseThrow); } + + /** + * Creates and returns a copy of this {@code NameSymbol} with the provided + * extra name. If this {@code NameSymbol} has a primary name, the provided + * name will become an other name, otherwise it will become the primary name. + * + * @since 2022-04-19 + */ + public final 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)); + } } \ No newline at end of file diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java new file mode 100644 index 0000000..b56356d --- /dev/null +++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java @@ -0,0 +1,95 @@ +/** + * @since 2020-08-26 + */ +package sevenUnitsGUI; + +import java.util.List; +import java.util.function.Predicate; + +import sevenUnits.unit.Metric; +import sevenUnits.unit.UnitPrefix; + +/** + * A rule that specifies whether prefix repetition is allowed + * + * @since 2020-08-26 + */ +public enum DefaultPrefixRepetitionRule implements Predicate> { + NO_REPETITION { + @Override + public boolean test(List prefixes) { + return prefixes.size() <= 1; + } + }, + NO_RESTRICTION { + @Override + public boolean test(List prefixes) { + return true; + } + }, + /** + * You are allowed to have any number of Yotta/Yocto followed by possibly one + * Kilo-Zetta/Milli-Zepto followed by possibly one Deca/Hecto. Same for + * reducing prefixes, don't mix magnifying and reducing. Non-metric + * (including binary) prefixes can't be repeated. + */ + COMPLEX_REPETITION { + @Override + public boolean test(List prefixes) { + // determine whether we are magnifying or reducing + final boolean magnifying; + if (prefixes.isEmpty()) + return true; + else if (prefixes.get(0).getMultiplier() > 1) { + magnifying = true; + } else { + magnifying = false; + } + + // if the first prefix is non-metric (including binary prefixes), + // assume we are using non-metric prefixes + // non-metric prefixes are allowed, but can't be repeated. + if (!Metric.DECIMAL_PREFIXES.contains(prefixes.get(0))) + return NO_REPETITION.test(prefixes); + + int part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto, + // 2=deka,hecto,deci,centi + + for (final UnitPrefix prefix : prefixes) { + // check that the current prefix is metric and appropriately + // magnifying/reducing + if (!Metric.DECIMAL_PREFIXES.contains(prefix)) + return false; + if (magnifying != prefix.getMultiplier() > 1) + return false; + + // check if the current prefix is correct + // since part is set *after* this check, part designates the state + // of the *previous* prefix + switch (part) { + case 0: + // do nothing, any prefix is valid after a yotta + break; + case 1: + // after a kilo-zetta, only deka/hecto are valid + if (Metric.THOUSAND_PREFIXES.contains(prefix)) + return false; + break; + case 2: + // deka/hecto must be the last prefix, so this is always invalid + return false; + } + + // set part + if (Metric.YOTTA.equals(prefix) || Metric.YOCTO.equals(prefix)) { + part = 0; + } else if (Metric.THOUSAND_PREFIXES.contains(prefix)) { + part = 1; + } else { + part = 2; + } + } + return true; + } + }; +} diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index f4f3e3a..fd050b7 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -16,8 +16,10 @@ */ package sevenUnitsGUI; +import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; @@ -30,7 +32,6 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import sevenUnits.ProgramInfo; import sevenUnits.unit.BaseDimension; @@ -139,13 +140,25 @@ public final class Presenter { return Presenter.class.getResourceAsStream(filepath); } + /** + * @return true iff a and b have any elements in common + * @since 2022-04-19 + */ + private static final boolean sharesAnyElements(Set a, Set b) { + for (final Object e : a) { + if (b.contains(e)) + return true; + } + return false; + } + /** * @return {@code line} with any comments removed. * @since 2021-03-13 */ private static final String withoutComments(String line) { final int index = line.indexOf('#'); - return index == -1 ? line : line.substring(index); + return index == -1 ? line : line.substring(0, index); } // ====== SETTINGS ====== @@ -171,14 +184,15 @@ public final class Presenter { * of unit conversions will be put into this function, and the resulting * string will be used in the output. */ - private Function numberDisplayRule; + 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; + private Predicate> prefixRepetitionRule = DefaultPrefixRepetitionRule.NO_RESTRICTION; /** * The set of units that is considered neither metric nor nonmetric for the @@ -192,13 +206,13 @@ public final class Presenter { * removed from the From unit list and imperial/USC units removed from the To * unit list. */ - private boolean oneWayConversionEnabled; + 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; + private boolean showDuplicates = false; /** * Creates a Presenter @@ -245,7 +259,19 @@ public final class Presenter { } // set default settings temporarily - this.numberDisplayRule = StandardDisplayRules.uncertaintyBased(); + this.loadSettings(DEFAULT_SETTINGS_FILEPATH); + + // 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", + this.database.unitMapPrefixless(false).size(), + this.database.unitMapPrefixless(true).size(), + this.database.unitMapPrefixless(false).values().stream() + .filter(isFullBase).count()); } /** @@ -455,19 +481,36 @@ public final class Presenter { return this.numberParsingRule; } + /** + * @return the rule that determines whether a set of prefixes is valid + * @since 2022-04-19 + */ + public Predicate> getPrefixRepetitionRule() { + return this.prefixRepetitionRule; + } + + /** + * @return the view associated with this presenter + * @since 2022-04-19 + */ + public View getView() { + return this.view; + } + /** * @return whether or not the provided unit is semi-metric (i.e. an * exception) * @since 2022-04-16 */ - private final boolean isSemiMetric(Unit u) { + final boolean isSemiMetric(Unit u) { // determine if u is an exception final var primaryName = u.getPrimaryName(); final var symbol = u.getSymbol(); return primaryName.isPresent() && this.metricExceptions.contains(primaryName.orElseThrow()) || symbol.isPresent() - && this.metricExceptions.contains(symbol.orElseThrow()); + && this.metricExceptions.contains(symbol.orElseThrow()) + || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } /** @@ -477,7 +520,48 @@ public final class Presenter { * @param settingsFile file settings should be loaded from * @since 2021-12-15 */ - void loadSettings(Path settingsFile) {} + void loadSettings(Path settingsFile) { + try { + // read file line by line + final int lineNum = 0; + for (final String line : Files.readAllLines(settingsFile)) { + final int equalsIndex = line.indexOf('='); + if (equalsIndex == -1) + throw new IllegalStateException( + "Settings file is malformed at line " + lineNum); + + final String param = line.substring(0, equalsIndex); + final String value = line.substring(equalsIndex + 1); + + switch (param) { + // set manually to avoid the unnecessary saving of the non-manual + // methods + case "number_display_rule": + this.numberDisplayRule = StandardDisplayRules + .getStandardRule(value); + break; + case "prefix_rule": + this.prefixRepetitionRule = DefaultPrefixRepetitionRule + .valueOf(value); + this.database.setPrefixRepetitionRule(this.prefixRepetitionRule); + break; + case "one_way": + this.oneWayConversionEnabled = Boolean.valueOf(value); + break; + case "include_duplicates": + this.showDuplicates = Boolean.valueOf(value); + break; + default: + System.err.printf("Warning: unrecognized setting \"%s\".%n", + param); + break; + } + } + if (this.view.getPresenter() != null) { + this.updateView(); + } + } catch (final IOException e) {} + } /** * @return true iff the One-Way Conversion feature is available (views that @@ -519,13 +603,38 @@ public final class Presenter { String.valueOf(prefix.getMultiplier()))); } + /** + * Saves the presenter's current settings to its default filepath. + * + * @since 2022-04-19 + */ + public void saveSettings() { + this.saveSettings(DEFAULT_SETTINGS_FILEPATH); + } + /** * Saves the presenter's settings to the user settings file. * * @param settingsFile file settings should be saved to * @since 2021-12-15 */ - void saveSettings(Path settingsFile) {} + void saveSettings(Path settingsFile) { + try (BufferedWriter writer = Files.newBufferedWriter(settingsFile)) { + writer.write(String.format("number_display_rule=%s\n", + 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)); + } catch (final IOException e) { + e.printStackTrace(); + this.view.showErrorMessage("I/O Error", + "Error occurred while saving settings: " + + e.getLocalizedMessage()); + } + } /** * @param numberDisplayRule the new rule that will be used by this presenter @@ -559,6 +668,17 @@ public final class Presenter { this.updateView(); } + /** + * @param prefixRepetitionRule the rule that determines whether a set of + * prefixes is valid + * @since 2022-04-19 + */ + public void setPrefixRepetitionRule( + Predicate> prefixRepetitionRule) { + this.prefixRepetitionRule = prefixRepetitionRule; + this.database.setPrefixRepetitionRule(prefixRepetitionRule); + } + /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 @@ -608,9 +728,7 @@ public final class Presenter { public void updateView() { if (this.view instanceof UnitConversionView) { final UnitConversionView ucview = (UnitConversionView) this.view; - final ObjectProduct viewDimension = this.database - .getDimension(((UnitConversionView) this.view) - .getSelectedDimensionName().orElseThrow()); + final var selectedDimensionName = ucview.getSelectedDimensionName(); // load units & prefixes into viewers this.view.setViewableUnitNames( @@ -619,29 +737,34 @@ public final class Presenter { this.database.prefixMap(this.showDuplicates).keySet()); // get From and To units - Stream fromUnits = this.database - .unitMapPrefixless(this.showDuplicates).entrySet().stream() - .map(Map.Entry::getValue) - .filter(u -> viewDimension.equals(u.getDimension())); + var fromUnits = this.database.unitMapPrefixless(this.showDuplicates) + .entrySet().stream(); + var toUnits = this.database.unitMapPrefixless(this.showDuplicates) + .entrySet().stream(); - Stream toUnits = this.database - .unitMapPrefixless(this.showDuplicates).entrySet().stream() - .map(Map.Entry::getValue) - .filter(u -> viewDimension.equals(u.getDimension())); + // filter by dimension, if one is selected + if (selectedDimensionName.isPresent()) { + final var viewDimension = this.database + .getDimension(selectedDimensionName.orElseThrow()); + fromUnits = fromUnits.filter( + u -> viewDimension.equals(u.getValue().getDimension())); + 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, + fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(), this::isSemiMetric) != UnitType.METRIC); - toUnits = toUnits.filter(u -> UnitType.getType(u, + toUnits = toUnits.filter(u -> UnitType.getType(u.getValue(), this::isSemiMetric) != UnitType.NON_METRIC); } // set unit names ucview.setFromUnitNames( - fromUnits.map(Unit::getName).collect(Collectors.toSet())); + fromUnits.map(Map.Entry::getKey).collect(Collectors.toSet())); ucview.setToUnitNames( - toUnits.map(Unit::getName).collect(Collectors.toSet())); + toUnits.map(Map.Entry::getKey).collect(Collectors.toSet())); } } diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 098c374..c8e69ee 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -543,39 +543,49 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { .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 (this.presenter.prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) { -// noRepetition.setSelected(true); -// } -// noRepetition -// .addActionListener(e -> this.presenter.setPrefixRepetitionRule( -// DefaultPrefixRepetitionRule.NO_REPETITION)); + if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) { + noRepetition.setSelected(true); + } + noRepetition.addActionListener(e -> { + this.presenter.setPrefixRepetitionRule( + DefaultPrefixRepetitionRule.NO_REPETITION); + this.presenter.saveSettings(); + }); prefixRuleButtons.add(noRepetition); prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); final JRadioButton noRestriction = new JRadioButton("No Restriction"); -// if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) { -// noRestriction.setSelected(true); -// } -// noRestriction -// .addActionListener(e -> this.presenter.setPrefixRepetitionRule( -// DefaultPrefixRepetitionRule.NO_RESTRICTION)); + if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) { + noRestriction.setSelected(true); + } + noRestriction.addActionListener(e -> { + this.presenter.setPrefixRepetitionRule( + DefaultPrefixRepetitionRule.NO_RESTRICTION); + this.presenter.saveSettings(); + }); prefixRuleButtons.add(noRestriction); prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); final JRadioButton customRepetition = new JRadioButton( "Complex Repetition"); -// if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) { -// customRepetition.setSelected(true); -// } -// customRepetition -// .addActionListener(e -> this.presenter.setPrefixRepetitionRule( -// DefaultPrefixRepetitionRule.COMPLEX_REPETITION)); + if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) { + customRepetition.setSelected(true); + } + customRepetition.addActionListener(e -> { + this.presenter.setPrefixRepetitionRule( + DefaultPrefixRepetitionRule.COMPLEX_REPETITION); + this.presenter.saveSettings(); + }); prefixRuleButtons.add(customRepetition); prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2) .setAnchor(GridBagConstraints.LINE_START).build()); @@ -628,16 +638,22 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { final JCheckBox oneWay = new JCheckBox("Convert One Way Only"); oneWay.setSelected(this.presenter.oneWayConversionEnabled()); - oneWay.addItemListener(e -> this.presenter.setOneWayConversionEnabled( - e.getStateChange() == ItemEvent.SELECTED)); + oneWay.addItemListener(e -> { + this.presenter.setOneWayConversionEnabled( + e.getStateChange() == ItemEvent.SELECTED); + this.presenter.saveSettings(); + }); 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()); - showAllVariations.addItemListener(e -> this.presenter - .setShowDuplicates(e.getStateChange() == ItemEvent.SELECTED)); + showAllVariations.addItemListener(e -> { + this.presenter + .setShowDuplicates(e.getStateChange() == ItemEvent.SELECTED); + this.presenter.saveSettings(); + }); miscPanel.add(showAllVariations, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); @@ -678,6 +694,11 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { return this.valueInput.getText(); } + @Override + public Presenter getPresenter() { + return this.presenter; + } + /** * @return the precision of the presenter's rounding rule, if that is * meaningful @@ -697,6 +718,17 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { return OptionalInt.empty(); } + /** + * @return presenter's prefix repetition rule + * @since 2022-04-19 + */ + private Optional getPresenterPrefixRule() { + final var prefixRule = this.presenter.getPrefixRepetitionRule(); + return prefixRule instanceof DefaultPrefixRepetitionRule + ? Optional.of((DefaultPrefixRepetitionRule) prefixRule) + : Optional.empty(); + } + /** * Determines which rounding type the presenter is currently using, if any. * @@ -830,5 +862,6 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { throw new AssertionError(); } this.presenter.setNumberDisplayRule(roundingRule); + this.presenter.saveSettings(); } } diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java index 60675e2..f951f44 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java @@ -16,6 +16,9 @@ */ package sevenUnitsGUI; +import java.math.RoundingMode; + +import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.UnitValue; /** @@ -24,6 +27,22 @@ import sevenUnits.unit.UnitValue; * @since 2022-04-09 */ public final class UnitConversionRecord { + /** + * Gets a {@code UnitConversionRecord} from two linear unit values + * + * @param input input unit & value + * @param output output unit & value + * @return unit conversion record + * @since 2022-04-09 + */ + public static UnitConversionRecord fromLinearValues(LinearUnitValue input, + LinearUnitValue output) { + return UnitConversionRecord.valueOf(input.getUnit().getName(), + output.getUnit().getName(), + input.getValue().toString(false, RoundingMode.HALF_EVEN), + output.getValue().toString(false, RoundingMode.HALF_EVEN)); + } + /** * Gets a {@code UnitConversionRecord} from two unit values * diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index da3749e..011e87f 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -29,6 +29,12 @@ import sevenUnits.utils.NameSymbol; * @since 2021-12-15 */ public interface View { + /** + * @return the presenter associated with this view + * @since 2022-04-19 + */ + Presenter getPresenter(); + /** * @return name of prefix currently being viewed * @since 2022-04-10 diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index dd9869d..9253ae5 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -196,30 +196,30 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { private final Presenter presenter; /** The dimensions available to select from */ - private Set dimensionNames; + private Set dimensionNames = Set.of(); /** The expression in the From field */ - private String fromExpression; + private String fromExpression = ""; /** The expression in the To field */ - private String toExpression; + private String toExpression = ""; /** * The user-provided string representing the value in {@code fromSelection} */ - private String inputValue; + private String inputValue = ""; /** The unit selected in the From selection */ - private Optional fromSelection; + private Optional fromSelection = Optional.empty(); /** The unit selected in the To selection */ - private Optional toSelection; + private Optional toSelection = Optional.empty(); /** The currently selected dimension */ - private Optional selectedDimensionName; + private Optional selectedDimensionName = Optional.empty(); /** The units available in the From selection */ - private Set fromUnits; + private Set fromUnits = Set.of(); /** The units available in the To selection */ - private Set toUnits; + private Set toUnits = Set.of(); /** The selected unit in the unit viewer */ - private Optional unitViewerSelection; + private Optional unitViewerSelection = Optional.empty(); /** The selected unit in the prefix viewer */ - private Optional prefixViewerSelection; + private Optional prefixViewerSelection = Optional.empty(); /** Saved outputs of all unit conversions */ private final List unitConversions; @@ -289,6 +289,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @return the presenter associated with tihs view * @since 2022-01-29 */ + @Override public Presenter getPresenter() { return this.presenter; } diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 362a5c9..f639329 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -19,6 +19,7 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.RoundingMode; import java.nio.file.Path; import java.util.List; import java.util.Set; @@ -32,10 +33,10 @@ import org.junit.jupiter.params.provider.MethodSource; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.BritishImperial; +import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitType; -import sevenUnits.unit.UnitValue; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.Nameable; import sevenUnits.utils.ObjectProduct; @@ -91,7 +92,7 @@ public final class PresenterTest { // test result final List outputs = viewBot .expressionConversionList(); - assertEquals("10000.0 m = 10.0 km", + assertEquals("10000.0 m = 10.00000 km", outputs.get(outputs.size() - 1).toString()); } @@ -120,12 +121,14 @@ public final class PresenterTest { * here (that's for the backend tests), I'm just testing that it correctly * calls the unit conversion system */ - final UnitValue expectedInput = UnitValue.of(Metric.METRE, 10000.0); - final UnitValue expectedOutput = expectedInput + final LinearUnitValue expectedInput = LinearUnitValue.of(Metric.METRE, + UncertainDouble.fromRoundedString("10000.0")); + final LinearUnitValue expectedOutput = expectedInput .convertTo(Metric.KILOMETRE); - final UnitConversionRecord expectedUC = UnitConversionRecord - .fromValues(expectedInput, expectedOutput); - + 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()); } @@ -182,13 +185,11 @@ public final class PresenterTest { // test that units are removed from each side when one-way conversion is // enabled presenter.setOneWayConversionEnabled(true); - presenter.updateView(); - assertEquals(metricNames, viewBot.getFromUnitNames()); - assertEquals(nonMetricNames, viewBot.getToUnitNames()); + assertEquals(nonMetricNames, viewBot.getFromUnitNames()); + assertEquals(metricNames, viewBot.getToUnitNames()); // test that units are kept when one-way conversion is disabled presenter.setOneWayConversionEnabled(false); - presenter.updateView(); assertEquals(allNames, viewBot.getFromUnitNames()); assertEquals(allNames, viewBot.getToUnitNames()); } @@ -243,7 +244,7 @@ public final class PresenterTest { // test the result of the rounding final String expectedOutputString = roundingRule - .apply(UncertainDouble.of(12.3456789, 0)); + .apply(UncertainDouble.fromRoundedString("12.3456789")); final String actualOutputString = viewBot.unitConversionList().get(0) .outputValueString(); assertEquals(expectedOutputString, actualOutputString); diff --git a/src/test/resources/test-settings.txt b/src/test/resources/test-settings.txt new file mode 100644 index 0000000..932221e --- /dev/null +++ b/src/test/resources/test-settings.txt @@ -0,0 +1,4 @@ +number_display_rule=Round to 11 significant figures +prefix_rule=NO_RESTRICTION +one_way=true +include_duplicates=true -- cgit v1.2.3