summaryrefslogtreecommitdiff
path: root/src/main/java/sevenUnitsGUI/Presenter.java
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2024-08-15 16:51:45 -0500
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2024-08-22 11:45:36 -0500
commite74668b91c247e7395362e8e411c3e1a13ec10d1 (patch)
treec5d70af1cf84ee6b1c8012745ce1288d8050a1b6 /src/main/java/sevenUnitsGUI/Presenter.java
parentfbb2f4a997e7e7043a2bdf15303b88907f6bcbc6 (diff)
Add ability to convert expression to sum of units
Diffstat (limited to 'src/main/java/sevenUnitsGUI/Presenter.java')
-rw-r--r--src/main/java/sevenUnitsGUI/Presenter.java606
1 files changed, 341 insertions, 265 deletions
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<UncertainDouble, String> numberDisplayRule) {
+ if (numberDisplayRule instanceof FixedDecimals)
+ return String.format("FIXED_DECIMALS %d",
+ ((FixedDecimals) numberDisplayRule).decimalPlaces());
+ else if (numberDisplayRule instanceof FixedPrecision)
+ return String.format("FIXED_PRECISION %d",
+ ((FixedPrecision) numberDisplayRule).significantFigures());
+ else if (numberDisplayRule instanceof UncertaintyBased)
+ return "UNCERTAINTY_BASED";
+ else
+ return numberDisplayRule.toString();
+ }
+
/**
* @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<String> getLinesFromResource(String filename) {
final List<String> 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<String, String> parseSettingLine(String line) {
+ final int equalsIndex = line.indexOf('=');
+ if (equalsIndex == -1)
+ throw new IllegalStateException(
+ "Settings file is malformed at line: " + line);
+
+ final String param = line.substring(0, equalsIndex);
+ final String value = line.substring(equalsIndex + 1);
+
+ return Map.entry(param, value);
+ }
+
+ /** 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.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
+ if (PrefixSearchRule.NO_PREFIXES.equals(searchRule))
+ return "NO_PREFIXES";
+ else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule))
+ return "COMMON_PREFIXES";
+ else if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule))
+ return "ALL_METRIC_PREFIXES";
+ else
+ return searchRule.toString();
+ }
+
+ // ====== 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. <b>Not implemented yet.</b>
*/
private Function<String, UncertainDouble> numberParsingRule;
-
+
/**
* The rule used for displaying the results of unit conversions. The result
* of unit conversions will be put into this function, and the resulting
@@ -216,41 +252,41 @@ public final class Presenter {
*/
private Function<UncertainDouble, String> numberDisplayRule = StandardDisplayRules
.uncertaintyBased();
-
+
/**
* A predicate that determines whether or not a certain combination of
* prefixes is allowed. If it returns false, a combination of prefixes will
* not be allowed. Prefixes are put in the list from right to left.
*/
private Predicate<List<UnitPrefix>> prefixRepetitionRule = DefaultPrefixRepetitionRule.NO_RESTRICTION;
-
+
/**
* A rule that accepts a prefixless name-unit pair and returns a map mapping
* names to prefixed versions of that unit (including the unit itself) that
* should be searchable.
*/
private Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule = PrefixSearchRule.NO_PREFIXES;
-
+
/**
* The set of units that is considered neither metric nor nonmetric for the
* purposes of the metric-imperial one-way conversion. These units are
* included in both From and To, even if One Way Conversion is enabled.
*/
private final Set<String> metricExceptions;
-
+
/**
* If this is true, views that show units as a list will have metric units
* removed from the From unit list and imperial/USC units removed from the To
* unit list.
*/
private boolean oneWayConversionEnabled = false;
-
+
/**
* If this is false, duplicate units and prefixes will be removed from the
* unit view in views that show units as a list to choose from.
*/
private boolean showDuplicates = false;
-
+
/**
* Creates a Presenter
*
@@ -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<Unit> isFullBase = unit -> unit instanceof LinearUnit
&& ((LinearUnit) unit).isBase();
-
+
// print out unit counts
System.out.printf(
"Successfully loaded %d units with %d unit names (%d base units).%n",
@@ -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<UnitConversionRecord> 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<UnitConversionRecord> 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<UnitConversionRecord> 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<LinearUnit> 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<LinearUnitValue> 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<String> fromUnitOptional = ucview.getFromSelection();
final Optional<String> toUnitOptional = ucview.getToSelection();
final String inputValueString = ucview.getInputValue();
-
+
// extract values from optionals
final String fromUnitString, toUnitString;
if (fromUnitOptional.isPresent()) {
@@ -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<UncertainDouble, String> 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<String, UncertainDouble> 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<List<UnitPrefix>> 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.Entry<String, LinearUnit>, Map<String, LinearUnit>> 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<String> 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<String, String> setting : settingsFromFile(settingsFile)) {
+ for (final Map.Entry<String, String> 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<Map.Entry<String, String>> settingsFromFile(Path settingsFile) {
- try (Stream<String> lines = Files.lines(settingsFile)) {
- return lines.map(Presenter::withoutComments)
- .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine)
- .toList();
- } catch (final IOException e) {
- this.view.showErrorMessage("Settings Loading Error",
- "Error loading settings file. Using default settings.");
- return null;
- }
- }
-
- private static Map.Entry<String, String> parseSettingLine(String line) {
- final int equalsIndex = line.indexOf('=');
- if (equalsIndex == -1)
- throw new IllegalStateException(
- "Settings file is malformed at line: " + line);
-
- final String param = line.substring(0, equalsIndex);
- final String value = line.substring(equalsIndex + 1);
-
- return Map.entry(param, value);
- }
-
- private void setSearchRuleFromString(String ruleString) {
- switch (ruleString) {
- case "NO_PREFIXES":
- this.searchRule = PrefixSearchRule.NO_PREFIXES;
- break;
- case "COMMON_PREFIXES":
- this.searchRule = PrefixSearchRule.COMMON_PREFIXES;
- break;
- case "ALL_METRIC_PREFIXES":
- this.searchRule = PrefixSearchRule.ALL_METRIC_PREFIXES;
- break;
- default:
- System.err.printf(
- "Warning: unrecognized value for search_prefix_rule: %s\n",
- ruleString);
- }
- }
-
- private void setDisplayRuleFromString(String ruleString) {
- String[] tokens = ruleString.split(" ");
- switch (tokens[0]) {
- case "FIXED_DECIMALS":
- final int decimals = Integer.parseInt(tokens[1]);
- this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals);
- break;
- case "FIXED_PRECISION":
- final int sigDigs = Integer.parseInt(tokens[1]);
- this.numberDisplayRule = StandardDisplayRules.fixedPrecision(sigDigs);
- break;
- case "UNCERTAINTY_BASED":
- this.numberDisplayRule = StandardDisplayRules.uncertaintyBased();
- break;
- default:
- this.numberDisplayRule = StandardDisplayRules
- .getStandardRule(ruleString);
- break;
- }
- }
-
- private void loadExceptionFile(Path exceptionFile) {
- try (Stream<String> lines = Files.lines(exceptionFile)) {
- lines.map(Presenter::withoutComments)
- .forEach(this.metricExceptions::add);
- } catch (IOException e) {
- this.view.showErrorMessage("File Load Error",
- "Error loading configured metric exception file \""
- + exceptionFile + "\": " + e.getLocalizedMessage());
- }
- }
-
+
/**
* @return true iff the One-Way Conversion feature is available (views that
* show units as a list will have metric units removed from the From
@@ -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<String> 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.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
- if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) {
- return "NO_PREFIXES";
- } else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) {
- return "COMMON_PREFIXES";
- } else if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule)) {
- return "ALL_METRIC_PREFIXES";
- } else
- return searchRule.toString();
- }
-
- private static String displayRuleToString(
- Function<UncertainDouble, String> numberDisplayRule) {
- if (numberDisplayRule instanceof FixedDecimals) {
- return String.format("FIXED_DECIMALS %d",
- ((FixedDecimals) numberDisplayRule).decimalPlaces());
- } else if (numberDisplayRule instanceof FixedPrecision) {
- return String.format("FIXED_PRECISION %d",
- ((FixedPrecision) numberDisplayRule).significantFigures());
- } else if (numberDisplayRule instanceof UncertaintyBased) {
- return "UNCERTAINTY_BASED";
- } else
- return numberDisplayRule.toString();
- }
-
+
/**
* @param numberDisplayRule the new rule that will be used by this presenter
* to convert numbers into strings
@@ -838,7 +856,7 @@ public final class Presenter {
Function<UncertainDouble, String> numberDisplayRule) {
this.numberDisplayRule = numberDisplayRule;
}
-
+
/**
* @param numberParsingRule the new rule that will be used by this presenter
* to convert strings into numbers
@@ -849,7 +867,7 @@ public final class Presenter {
Function<String, UncertainDouble> 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.Entry<String, LinearUnit>, Map<String, LinearUnit>> 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<Map.Entry<String, String>> settingsFromFile(Path settingsFile) {
+ try (Stream<String> lines = Files.lines(settingsFile)) {
+ return lines.map(Presenter::withoutComments)
+ .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine)
+ .toList();
+ } catch (final IOException e) {
+ this.view.showErrorMessage("Settings Loading Error",
+ "Error loading settings file. Using default settings.");
+ return null;
+ }
+ }
+
/**
* 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;
+ }
+ }
}