summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2020-08-04 16:08:22 -0500
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2020-08-04 16:08:22 -0500
commit77c7c962a6ed810b12685aa9ace4bd8a62761cea (patch)
tree49adb3dabd4c623f6e5f956ac7f4d3ba1ec8f7f2
parentcf76cf66ea2039cd3e3052c940784dd88a87e2bd (diff)
Made UnitConverterGUI use UnitValue and LinearUnitValue
-rw-r--r--src/org/unitConverter/converterGUI/UnitConverterGUI.java687
-rw-r--r--src/org/unitConverter/unit/LinearUnitValue.java12
-rw-r--r--src/org/unitConverter/unit/UnitDatabase.java1163
3 files changed, 1119 insertions, 743 deletions
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 986cb52..8c70df4 100644
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -59,10 +59,13 @@ import org.unitConverter.math.ObjectProduct;
import org.unitConverter.unit.BaseDimension;
import org.unitConverter.unit.BritishImperial;
import org.unitConverter.unit.LinearUnit;
+import org.unitConverter.unit.LinearUnitValue;
+import org.unitConverter.unit.NameSymbol;
import org.unitConverter.unit.SI;
import org.unitConverter.unit.Unit;
import org.unitConverter.unit.UnitDatabase;
import org.unitConverter.unit.UnitPrefix;
+import org.unitConverter.unit.UnitValue;
/**
* @author Adrien Hopkins
@@ -74,20 +77,10 @@ final class UnitConverterGUI {
* A tab in the View.
*/
private enum Pane {
- UNIT_CONVERTER, EXPRESSION_CONVERTER, UNIT_VIEWER, PREFIX_VIEWER, SETTINGS;
+ UNIT_CONVERTER, EXPRESSION_CONVERTER, UNIT_VIEWER, PREFIX_VIEWER,
+ SETTINGS;
}
- /**
- * Different types of rounding.
- *
- * Significant digits: Rounds to a number of digits. i.e. with precision 5, 12345.6789 rounds to 12346.
- * Decimal places: Rounds to a number of digits after the decimal point, i.e. with precision 5, 12345.6789 rounds to 12345.67890.
- * Scientific: Rounds based on the number of digits and operations, following standard scientific rounding.
- */
- private static enum RoundingType {
- SIGNIFICANT_DIGITS, DECIMAL_PLACES, SCIENTIFIC;
- }
-
private static class Presenter {
/**
* Adds default units and dimensions to a database.
@@ -110,40 +103,40 @@ final class UnitConverterGUI {
// nonlinear units - must be loaded manually
database.addUnit("tempCelsius", SI.CELSIUS);
database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT);
-
+
// load initial dimensions
database.addDimension("LENGTH", SI.Dimensions.LENGTH);
database.addDimension("MASS", SI.Dimensions.MASS);
database.addDimension("TIME", SI.Dimensions.TIME);
database.addDimension("TEMPERATURE", SI.Dimensions.TEMPERATURE);
}
-
+
/** The presenter's associated view. */
private final View view;
-
+
/** The units known by the program. */
private final UnitDatabase database;
-
+
/** The names of all of the units */
private final List<String> unitNames;
-
+
/** The names of all of the prefixes */
private final List<String> prefixNames;
-
+
/** The names of all of the dimensions */
private final List<String> dimensionNames;
-
+
private final Comparator<String> prefixNameComparator;
-
+
/*
- * Rounding-related settings. I am using my own system, and not MathContext,
- * because MathContext does not support decimal place based or scientific
- * rounding, only significant digit based rounding.
+ * Rounding-related settings. I am using my own system, and not
+ * MathContext, because MathContext does not support decimal place based
+ * or scientific rounding, only significant digit based rounding.
*/
private int precision = 6;
-
+
private RoundingType roundingType = RoundingType.SIGNIFICANT_DIGITS;
-
+
/**
* Creates the presenter.
*
@@ -153,14 +146,14 @@ final class UnitConverterGUI {
*/
Presenter(final View view) {
this.view = view;
-
+
// load initial units
this.database = new UnitDatabase();
Presenter.addDefaults(this.database);
-
+
this.database.loadUnitsFile(new File("unitsfile.txt"));
this.database.loadDimensionFile(new File("dimensionfile.txt"));
-
+
// a comparator that can be used to compare prefix names
// any name that does not exist is less than a name that does.
// otherwise, they are compared by value
@@ -169,37 +162,43 @@ final class UnitConverterGUI {
return -1;
else if (!Presenter.this.database.containsPrefixName(o2))
return 1;
-
+
final UnitPrefix p1 = Presenter.this.database.getPrefix(o1);
final UnitPrefix p2 = Presenter.this.database.getPrefix(o2);
-
+
if (p1.getMultiplier() < p2.getMultiplier())
return -1;
else if (p1.getMultiplier() > p2.getMultiplier())
return 1;
-
+
return o1.compareTo(o2);
};
-
- this.unitNames = new ArrayList<>(this.database.unitMapPrefixless().keySet());
+
+ this.unitNames = new ArrayList<>(
+ this.database.unitMapPrefixless().keySet());
this.unitNames.sort(null); // sorts it using Comparable
-
+
this.prefixNames = new ArrayList<>(this.database.prefixMap().keySet());
- this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator
-
- this.dimensionNames = new DelegateListModel<>(new ArrayList<>(this.database.dimensionMap().keySet()));
+ this.prefixNames.sort(this.prefixNameComparator); // sorts it using my
+ // comparator
+
+ this.dimensionNames = new DelegateListModel<>(
+ new ArrayList<>(this.database.dimensionMap().keySet()));
this.dimensionNames.sort(null); // sorts it using Comparable
-
+
// a Predicate that returns true iff the argument is a full base unit
- final Predicate<Unit> isFullBase = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase();
-
+ 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",
+ System.out.printf(
+ "Successfully loaded %d units with %d unit names (%d base units).%n",
new HashSet<>(this.database.unitMapPrefixless().values()).size(),
this.database.unitMapPrefixless().size(),
- new HashSet<>(this.database.unitMapPrefixless().values()).stream().filter(isFullBase).count());
+ new HashSet<>(this.database.unitMapPrefixless().values())
+ .stream().filter(isFullBase).count());
}
-
+
/**
* Converts in the dimension-based converter
*
@@ -209,7 +208,8 @@ final class UnitConverterGUI {
public final void convertDimensionBased() {
final String fromSelection = this.view.getFromSelection();
if (fromSelection == null) {
- this.view.showErrorDialog("Error", "No unit selected in From field");
+ this.view.showErrorDialog("Error",
+ "No unit selected in From field");
return;
}
final String toSelection = this.view.getToSelection();
@@ -217,31 +217,35 @@ final class UnitConverterGUI {
this.view.showErrorDialog("Error", "No unit selected in To field");
return;
}
-
+
final Unit from = this.database.getUnit(fromSelection);
- final Unit to = this.database.getUnit(toSelection);
-
- final double beforeValue;
+ final Unit to = this.database.getUnit(toSelection)
+ .withName(NameSymbol.ofName(toSelection));
+
+ final UnitValue beforeValue;
try {
- beforeValue = this.view.getDimensionConverterInput();
- } catch (ParseException e) {
- this.view.showErrorDialog("Error", "Error in parsing: " + e.getMessage());
+ beforeValue = UnitValue.of(from,
+ this.view.getDimensionConverterInput());
+ } catch (final ParseException e) {
+ this.view.showErrorDialog("Error",
+ "Error in parsing: " + e.getMessage());
return;
}
- final double value = from.convertTo(to, beforeValue);
-
- final String output = this.getRoundedString(new BigDecimal(value));
-
- this.view.setDimensionConverterOutputText(String.format("%s %s = %s %s",
- this.view.getDimensionConverterText(), fromSelection, output, toSelection));
+ final UnitValue value = beforeValue.convertTo(to);
+
+ final String output = this.getRoundedString(value);
+
+ this.view.setDimensionConverterOutputText(
+ String.format("%s = %s", beforeValue, output));
}
-
+
/**
* Runs whenever the convert button is pressed.
*
* <p>
- * Reads and parses a unit expression from the from and to boxes, then converts
- * {@code from} to {@code to}. Any errors are shown in JOptionPanes.
+ * Reads and parses a unit expression from the from and to boxes, then
+ * converts {@code from} to {@code to}. Any errors are shown in
+ * JOptionPanes.
* </p>
*
* @since 2019-01-26
@@ -250,64 +254,78 @@ final class UnitConverterGUI {
public final void convertExpressions() {
final String fromUnitString = this.view.getFromText();
final String toUnitString = this.view.getToText();
-
+
if (fromUnitString.isEmpty()) {
- this.view.showErrorDialog("Parse Error", "Please enter a unit expression in the From: box.");
+ this.view.showErrorDialog("Parse Error",
+ "Please enter a unit expression in the From: box.");
return;
}
if (toUnitString.isEmpty()) {
- this.view.showErrorDialog("Parse Error", "Please enter a unit expression in the To: box.");
+ this.view.showErrorDialog("Parse Error",
+ "Please enter a unit expression in the To: box.");
return;
}
-
- // try to parse from
- final Unit from;
+
+ final LinearUnitValue from;
+ final Unit to;
try {
- from = this.database.getUnitFromExpression(fromUnitString);
+ from = this.database.evaluateUnitExpression(fromUnitString);
} catch (final IllegalArgumentException e) {
- this.view.showErrorDialog("Parse Error", "Could not recognize text in From entry: " + e.getMessage());
+ this.view.showErrorDialog("Parse Error",
+ "Could not recognize text in From entry: " + e.getMessage());
return;
}
-
- final double value;
- // try to parse to
- final Unit to;
try {
- if (this.database.containsUnitName(toUnitString)) {
- // if it's a unit, convert to that
- to = this.database.getUnit(toUnitString);
- } else {
- to = this.database.getUnitFromExpression(toUnitString);
- }
+ to = this.database.getUnitFromExpression(toUnitString);
} catch (final IllegalArgumentException e) {
- this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage());
+ this.view.showErrorDialog("Parse Error",
+ "Could not recognize text in To entry: " + e.getMessage());
return;
}
-
- if (from.canConvertTo(to)) {
- value = from.convertTo(to, 1);
-
- // round value
- final String output = this.getRoundedString(new BigDecimal(value));
-
- this.view.setExpressionConverterOutputText(
- String.format("%s = %s %s", fromUnitString, output, toUnitString));
- } else if (from instanceof LinearUnit && SI.ONE.dividedBy((LinearUnit) from).canConvertTo(to)) {
- // reciprocal conversion (like seconds to hertz)
- value = SI.ONE.dividedBy((LinearUnit) from).convertTo(to, 1);
-
- // round value
- final String output = this.getRoundedString(new BigDecimal(value));
-
+
+ if (to instanceof LinearUnit) {
+ // convert to LinearUnitValue
+ final LinearUnitValue from2;
+ final LinearUnit to2 = (LinearUnit) to;
+ final boolean useSlash;
+
+ if (from.canConvertTo(to2)) {
+ from2 = from;
+ useSlash = false;
+ } else if (LinearUnitValue.ONE.dividedBy(from).canConvertTo(to2)) {
+ from2 = LinearUnitValue.ONE.dividedBy(from);
+ useSlash = true;
+ } else {
+ // if I can't convert, leave
+ this.view.showErrorDialog("Conversion Error",
+ String.format("Cannot convert between %s and %s",
+ fromUnitString, toUnitString));
+ return;
+ }
+
+ final LinearUnitValue converted = from2.convertTo(to2);
this.view.setExpressionConverterOutputText(
- String.format("1 / %s = %s %s", fromUnitString, output, toUnitString));
+ (useSlash ? "1 / " : "") + String.format("%s = %s",
+ fromUnitString, this.getRoundedString(converted)));
+ return;
} else {
- // if I can't convert, leave
- this.view.showErrorDialog("Conversion Error",
- String.format("Cannot convert between %s and %s", fromUnitString, toUnitString));
+ // convert to UnitValue
+ final UnitValue from2 = from.asUnitValue();
+ if (from2.canConvertTo(to)) {
+ final UnitValue converted = from2.convertTo(to);
+
+ this.view
+ .setExpressionConverterOutputText(String.format("%s = %s",
+ fromUnitString, this.getRoundedString(converted)));
+ } else {
+ // if I can't convert, leave
+ this.view.showErrorDialog("Conversion Error",
+ String.format("Cannot convert between %s and %s",
+ fromUnitString, toUnitString));
+ }
}
}
-
+
/**
* @return a list of all of the unit dimensions
* @since 2019-04-13
@@ -316,7 +334,7 @@ final class UnitConverterGUI {
public final List<String> dimensionNameList() {
return this.dimensionNames;
}
-
+
/**
* @return a comparator to compare prefix names
* @since 2019-04-14
@@ -325,20 +343,21 @@ final class UnitConverterGUI {
public final Comparator<String> getPrefixNameComparator() {
return this.prefixNameComparator;
}
-
+
/**
* @param value value to round
- * @return string of that value rounded to {@code significantDigits} significant
- * digits.
+ * @return string of that value rounded to {@code significantDigits}
+ * significant digits.
* @since 2019-04-14
* @since v0.2.0
*/
private final String getRoundedString(final BigDecimal value) {
// round value based on rounding type
final BigDecimal roundedValue;
- switch (roundingType) {
+ switch (this.roundingType) {
case DECIMAL_PLACES:
- roundedValue = value.setScale(precision, RoundingMode.HALF_EVEN);
+ roundedValue = value.setScale(this.precision,
+ RoundingMode.HALF_EVEN);
break;
case SCIENTIFIC:
throw new UnsupportedOperationException("Not yet implemented.");
@@ -350,7 +369,7 @@ final class UnitConverterGUI {
}
String output = roundedValue.toString();
-
+
// remove trailing zeroes
if (output.contains(".")) {
while (output.endsWith("0")) {
@@ -360,10 +379,68 @@ final class UnitConverterGUI {
output = output.substring(0, output.length() - 1);
}
}
-
+
return output;
}
-
+
+ /**
+ * Like {@link LinearUnitValue#toString(boolean)} with parameter
+ * {@code false}, but obeys this unit converter's rounding settings.
+ *
+ * @since 2020-08-04
+ */
+ private final String getRoundedString(final LinearUnitValue value) {
+ switch (this.roundingType) {
+ case DECIMAL_PLACES:
+ case SIGNIFICANT_DIGITS:
+ return this.getRoundedString(value.asUnitValue());
+ case SCIENTIFIC:
+ return value.toString(false);
+ default:
+ throw new AssertionError("Invalid switch condition.");
+ }
+ }
+
+ /**
+ * Like {@link UnitValue#toString()}, but obeys this unit converter's
+ * rounding settings.
+ *
+ * @since 2020-08-04
+ */
+ private final String getRoundedString(final UnitValue value) {
+ final BigDecimal unrounded = new BigDecimal(value.getValue());
+ final BigDecimal rounded;
+ int precision = this.precision;
+ switch (this.roundingType) {
+ case DECIMAL_PLACES:
+ rounded = unrounded.setScale(precision, RoundingMode.HALF_EVEN);
+ break;
+ case SCIENTIFIC:
+ precision = 12;
+ //$FALL-THROUGH$
+ case SIGNIFICANT_DIGITS:
+ rounded = unrounded
+ .round(new MathContext(precision, RoundingMode.HALF_EVEN));
+ break;
+ default:
+ throw new AssertionError("Invalid switch condition.");
+ }
+
+ String output = rounded.toString();
+
+ // remove trailing zeroes
+ if (output.contains(".")) {
+ while (output.endsWith("0")) {
+ output = output.substring(0, output.length() - 1);
+ }
+ if (output.endsWith(".")) {
+ output = output.substring(0, output.length() - 1);
+ }
+ }
+
+ return output + " " + value.getUnit().getPrimaryName().get();
+ }
+
/**
* @return a set of all prefix names in the database
* @since 2019-04-14
@@ -372,7 +449,7 @@ final class UnitConverterGUI {
public final Set<String> prefixNameSet() {
return this.database.prefixMap().keySet();
}
-
+
/**
* Runs whenever a prefix is selected in the viewer.
* <p>
@@ -388,11 +465,12 @@ final class UnitConverterGUI {
return;
else {
final UnitPrefix prefix = this.database.getPrefix(prefixName);
-
- this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier()));
+
+ this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s",
+ prefixName, prefix.getMultiplier()));
}
}
-
+
/**
* @param precision new value of precision
* @since 2019-01-15
@@ -401,10 +479,18 @@ final class UnitConverterGUI {
public final void setPrecision(final int precision) {
this.precision = precision;
}
-
+
+ /**
+ * @param roundingType the roundingType to set
+ * @since 2020-07-16
+ */
+ public final void setRoundingType(RoundingType roundingType) {
+ this.roundingType = roundingType;
+ }
+
/**
- * Returns true if and only if the unit represented by {@code unitName} has the
- * dimension represented by {@code dimensionName}.
+ * 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
@@ -412,12 +498,14 @@ final class UnitConverterGUI {
* @since 2019-04-13
* @since v0.2.0
*/
- public final boolean unitMatchesDimension(final String unitName, final String dimensionName) {
+ public final boolean unitMatchesDimension(final String unitName,
+ final String dimensionName) {
final Unit unit = this.database.getUnit(unitName);
- final ObjectProduct<BaseDimension> dimension = this.database.getDimension(dimensionName);
+ final ObjectProduct<BaseDimension> dimension = this.database
+ .getDimension(dimensionName);
return unit.getDimension().equals(dimension);
}
-
+
/**
* Runs whenever a unit is selected in the viewer.
* <p>
@@ -433,11 +521,11 @@ final class UnitConverterGUI {
return;
else {
final Unit unit = this.database.getUnit(unitName);
-
+
this.view.setUnitTextBoxText(unit.toString());
}
}
-
+
/**
* @return a set of all of the unit names
* @since 2019-04-14
@@ -446,16 +534,21 @@ final class UnitConverterGUI {
public final Set<String> unitNameSet() {
return this.database.unitMapPrefixless().keySet();
}
-
- /**
- * @param roundingType the roundingType to set
- * @since 2020-07-16
- */
- public final void setRoundingType(RoundingType roundingType) {
- this.roundingType = roundingType;
- }
}
-
+
+ /**
+ * Different types of rounding.
+ *
+ * Significant digits: Rounds to a number of digits. i.e. with precision 5,
+ * 12345.6789 rounds to 12346. Decimal places: Rounds to a number of digits
+ * after the decimal point, i.e. with precision 5, 12345.6789 rounds to
+ * 12345.67890. Scientific: Rounds based on the number of digits and
+ * operations, following standard scientific rounding.
+ */
+ private static enum RoundingType {
+ SIGNIFICANT_DIGITS, DECIMAL_PLACES, SCIENTIFIC;
+ }
+
private static class View {
private static final NumberFormat NUMBER_FORMATTER = new DecimalFormat();
@@ -465,7 +558,7 @@ final class UnitConverterGUI {
private final Presenter presenter;
/** The master pane containing all of the tabs. */
private final JTabbedPane masterPane;
-
+
// DIMENSION-BASED CONVERTER
/** The panel for inputting values in the dimension-based converter */
private final JTextField valueInput;
@@ -475,7 +568,7 @@ final class UnitConverterGUI {
private final SearchBoxList toSearch;
/** The output area in the dimension-based converter */
private final JTextArea dimensionBasedOutput;
-
+
// EXPRESSION-BASED CONVERTER
/** The "From" entry in the conversion panel */
private final JTextField fromEntry;
@@ -483,7 +576,7 @@ final class UnitConverterGUI {
private final JTextField toEntry;
/** The output area in the conversion panel */
private final JTextArea output;
-
+
// UNIT AND PREFIX VIEWERS
/** The searchable list of unit names in the unit viewer */
private final SearchBoxList unitNameList;
@@ -493,7 +586,7 @@ final class UnitConverterGUI {
private final JTextArea unitTextBox;
/** The text box for prefix data in the prefix viewer */
private final JTextArea prefixTextBox;
-
+
/**
* Creates the {@code View}.
*
@@ -505,7 +598,7 @@ final class UnitConverterGUI {
this.frame = new JFrame("Unit Converter");
this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.masterPane = new JTabbedPane();
-
+
// create the components
this.unitNameList = new SearchBoxList(this.presenter.unitNameSet());
this.prefixNameList = new SearchBoxList(this.presenter.prefixNameSet(),
@@ -519,13 +612,13 @@ final class UnitConverterGUI {
this.fromEntry = new JTextField();
this.toEntry = new JTextField();
this.output = new JTextArea(2, 32);
-
+
// create more components
this.initComponents();
-
+
this.frame.pack();
}
-
+
/**
* @return the currently selected pane.
* @throws AssertionError if no pane (or an invalid pane) is selected
@@ -546,19 +639,20 @@ final class UnitConverterGUI {
throw new AssertionError("No selected pane, or invalid pane.");
}
}
-
+
/**
* @return value in dimension-based converter
- * @throws ParseException
+ * @throws ParseException
* @since 2020-07-07
*/
public double getDimensionConverterInput() throws ParseException {
- Number value = NUMBER_FORMATTER.parse(this.valueInput.getText());
- if (value instanceof Double) {
+ final Number value = NUMBER_FORMATTER.parse(this.valueInput.getText());
+ if (value instanceof Double)
return (double) value;
- } else if (value instanceof Long) {
- return (((Long) value).longValue());
- } else throw new AssertionError();
+ else if (value instanceof Long)
+ return ((Long) value).longValue();
+ else
+ throw new AssertionError();
}
/**
@@ -569,7 +663,7 @@ final class UnitConverterGUI {
public String getDimensionConverterText() {
return this.valueInput.getText();
}
-
+
/**
* @return selection in "From" selector in dimension-based converter
* @since 2019-04-13
@@ -578,7 +672,7 @@ final class UnitConverterGUI {
public String getFromSelection() {
return this.fromSearch.getSelectedValue();
}
-
+
/**
* @return text in "From" box in converter panel
* @since 2019-01-15
@@ -587,7 +681,7 @@ final class UnitConverterGUI {
public String getFromText() {
return this.fromEntry.getText();
}
-
+
/**
* @return index of selected prefix in prefix viewer
* @since 2019-01-15
@@ -596,7 +690,7 @@ final class UnitConverterGUI {
public String getPrefixViewerSelection() {
return this.prefixNameList.getSelectedValue();
}
-
+
/**
* @return selection in "To" selector in dimension-based converter
* @since 2019-04-13
@@ -605,7 +699,7 @@ final class UnitConverterGUI {
public String getToSelection() {
return this.toSearch.getSelectedValue();
}
-
+
/**
* @return text in "To" box in converter panel
* @since 2019-01-26
@@ -614,7 +708,7 @@ final class UnitConverterGUI {
public String getToText() {
return this.toEntry.getText();
}
-
+
/**
* @return index of selected unit in unit viewer
* @since 2019-01-15
@@ -623,7 +717,7 @@ final class UnitConverterGUI {
public String getUnitViewerSelection() {
return this.unitNameList.getSelectedValue();
}
-
+
/**
* Starts up the application.
*
@@ -633,7 +727,7 @@ final class UnitConverterGUI {
public final void init() {
this.frame.setVisible(true);
}
-
+
/**
* Initializes the view's components.
*
@@ -643,199 +737,213 @@ final class UnitConverterGUI {
private final void initComponents() {
final JPanel masterPanel = new JPanel();
this.frame.add(masterPanel);
-
+
masterPanel.setLayout(new BorderLayout());
-
+
{ // pane with all of the tabs
masterPanel.add(this.masterPane, BorderLayout.CENTER);
-
+
{ // a panel for unit conversion using a selector
final JPanel convertUnitPanel = new JPanel();
this.masterPane.addTab("Convert Units", convertUnitPanel);
this.masterPane.setMnemonicAt(0, KeyEvent.VK_U);
-
+
convertUnitPanel.setLayout(new BorderLayout());
-
+
{ // panel for input part
final JPanel inputPanel = new JPanel();
convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
-
+
inputPanel.setLayout(new GridLayout(1, 3));
-
+
final JComboBox<String> dimensionSelector = new JComboBox<>(
- this.presenter.dimensionNameList().toArray(new String[0]));
+ this.presenter.dimensionNameList()
+ .toArray(new String[0]));
dimensionSelector.setSelectedItem("LENGTH");
-
+
// handle dimension filter
- final MutablePredicate<String> dimensionFilter = new MutablePredicate<>(s -> true);
-
+ final MutablePredicate<String> dimensionFilter = new MutablePredicate<>(
+ s -> true);
+
// panel for From things
inputPanel.add(this.fromSearch);
-
+
this.fromSearch.addSearchFilter(dimensionFilter);
-
- { // for dimension selector and arrow that represents conversion
+
+ { // for dimension selector and arrow that represents
+ // conversion
final JPanel inBetweenPanel = new JPanel();
inputPanel.add(inBetweenPanel);
-
+
inBetweenPanel.setLayout(new BorderLayout());
-
+
{ // dimension selector
- inBetweenPanel.add(dimensionSelector, BorderLayout.PAGE_START);
+ inBetweenPanel.add(dimensionSelector,
+ BorderLayout.PAGE_START);
}
-
+
{ // the arrow in the middle
final JLabel arrowLabel = new JLabel("->");
inBetweenPanel.add(arrowLabel, BorderLayout.CENTER);
}
}
-
+
// panel for To things
-
+
inputPanel.add(this.toSearch);
-
+
this.toSearch.addSearchFilter(dimensionFilter);
-
+
// code for dimension filter
dimensionSelector.addItemListener(e -> {
- dimensionFilter.setPredicate(string -> View.this.presenter.unitMatchesDimension(string,
- (String) dimensionSelector.getSelectedItem()));
+ dimensionFilter.setPredicate(string -> View.this.presenter
+ .unitMatchesDimension(string,
+ (String) dimensionSelector.getSelectedItem()));
this.fromSearch.reapplyFilter();
this.toSearch.reapplyFilter();
});
-
- // apply the item listener once because I have a default selection
- dimensionFilter.setPredicate(string -> View.this.presenter.unitMatchesDimension(string,
- (String) dimensionSelector.getSelectedItem()));
+
+ // apply the item listener once because I have a default
+ // selection
+ dimensionFilter.setPredicate(string -> View.this.presenter
+ .unitMatchesDimension(string,
+ (String) dimensionSelector.getSelectedItem()));
this.fromSearch.reapplyFilter();
this.toSearch.reapplyFilter();
}
-
+
{ // panel for submit and output, and also value entry
final JPanel outputPanel = new JPanel();
convertUnitPanel.add(outputPanel, BorderLayout.PAGE_END);
-
+
outputPanel.setLayout(new GridLayout(3, 1));
-
+
{ // unit input
final JPanel valueInputPanel = new JPanel();
outputPanel.add(valueInputPanel);
-
+
valueInputPanel.setLayout(new BorderLayout());
-
+
{ // prompt
- final JLabel valuePrompt = new JLabel("Value to convert: ");
- valueInputPanel.add(valuePrompt, BorderLayout.LINE_START);
+ final JLabel valuePrompt = new JLabel(
+ "Value to convert: ");
+ valueInputPanel.add(valuePrompt,
+ BorderLayout.LINE_START);
}
-
+
{ // value to convert
- valueInputPanel.add(this.valueInput, BorderLayout.CENTER);
+ valueInputPanel.add(this.valueInput,
+ BorderLayout.CENTER);
}
}
-
+
{ // button to convert
final JButton convertButton = new JButton("Convert");
outputPanel.add(convertButton);
-
- convertButton.addActionListener(e -> this.presenter.convertDimensionBased());
+
+ convertButton.addActionListener(
+ e -> this.presenter.convertDimensionBased());
convertButton.setMnemonic(KeyEvent.VK_ENTER);
}
-
+
{ // output of conversion
outputPanel.add(this.dimensionBasedOutput);
this.dimensionBasedOutput.setEditable(false);
}
}
}
-
+
{ // panel for unit conversion using expressions
final JPanel convertExpressionPanel = new JPanel();
- this.masterPane.addTab("Convert Unit Expressions", convertExpressionPanel);
+ this.masterPane.addTab("Convert Unit Expressions",
+ convertExpressionPanel);
this.masterPane.setMnemonicAt(1, KeyEvent.VK_E);
-
+
convertExpressionPanel.setLayout(new GridLayout(4, 1));
-
+
{ // panel for units to convert from
final JPanel fromPanel = new JPanel();
convertExpressionPanel.add(fromPanel);
-
+
fromPanel.setBorder(BorderFactory.createTitledBorder("From"));
fromPanel.setLayout(new GridLayout(1, 1));
-
+
{ // entry for units
fromPanel.add(this.fromEntry);
}
}
-
+
{ // panel for units to convert to
final JPanel toPanel = new JPanel();
convertExpressionPanel.add(toPanel);
-
+
toPanel.setBorder(BorderFactory.createTitledBorder("To"));
toPanel.setLayout(new GridLayout(1, 1));
-
+
{ // entry for units
toPanel.add(this.toEntry);
}
}
-
+
{ // button to convert
final JButton convertButton = new JButton("Convert");
convertExpressionPanel.add(convertButton);
-
- convertButton.addActionListener(e -> this.presenter.convertExpressions());
+
+ convertButton.addActionListener(
+ e -> this.presenter.convertExpressions());
convertButton.setMnemonic(KeyEvent.VK_ENTER);
}
-
+
{ // output of conversion
final JPanel outputPanel = new JPanel();
convertExpressionPanel.add(outputPanel);
-
- outputPanel.setBorder(BorderFactory.createTitledBorder("Output"));
+
+ outputPanel
+ .setBorder(BorderFactory.createTitledBorder("Output"));
outputPanel.setLayout(new GridLayout(1, 1));
-
+
{ // output
outputPanel.add(this.output);
this.output.setEditable(false);
}
}
}
-
+
{ // panel to look up units
final JPanel unitLookupPanel = new JPanel();
this.masterPane.addTab("Unit Viewer", unitLookupPanel);
this.masterPane.setMnemonicAt(2, KeyEvent.VK_V);
-
+
unitLookupPanel.setLayout(new GridLayout());
-
+
{ // search panel
unitLookupPanel.add(this.unitNameList);
-
- this.unitNameList.getSearchList()
- .addListSelectionListener(e -> this.presenter.unitNameSelected());
+
+ this.unitNameList.getSearchList().addListSelectionListener(
+ e -> this.presenter.unitNameSelected());
}
-
+
{ // the text box for unit's toString
unitLookupPanel.add(this.unitTextBox);
this.unitTextBox.setEditable(false);
this.unitTextBox.setLineWrap(true);
}
}
-
+
{ // panel to look up prefixes
final JPanel prefixLookupPanel = new JPanel();
this.masterPane.addTab("Prefix Viewer", prefixLookupPanel);
this.masterPane.setMnemonicAt(3, KeyEvent.VK_P);
-
+
prefixLookupPanel.setLayout(new GridLayout(1, 2));
-
+
{ // panel for listing and seaching
prefixLookupPanel.add(this.prefixNameList);
-
- this.prefixNameList.getSearchList()
- .addListSelectionListener(e -> this.presenter.prefixSelected());
+
+ this.prefixNameList.getSearchList().addListSelectionListener(
+ e -> this.presenter.prefixSelected());
}
-
+
{ // the text box for prefix's toString
prefixLookupPanel.add(this.prefixTextBox);
this.prefixTextBox.setEditable(false);
@@ -848,77 +956,104 @@ final class UnitConverterGUI {
this.masterPane.addTab("\u2699", new JScrollPane(settingsPanel));
this.masterPane.setMnemonicAt(4, KeyEvent.VK_S);
- settingsPanel.setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
+ settingsPanel.setLayout(
+ new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
{ // rounding settings
final JPanel roundingPanel = new JPanel();
settingsPanel.add(roundingPanel);
- roundingPanel.setBorder(new TitledBorder("Rounding Settings"));
+ roundingPanel
+ .setBorder(new TitledBorder("Rounding Settings"));
roundingPanel.setLayout(new GridBagLayout());
// rounding rule selection
final ButtonGroup roundingRuleButtons = new ButtonGroup();
final JLabel roundingRuleLabel = new JLabel("Rounding Rule:");
- roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
+ roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0)
+ .setAnchor(GridBagConstraints.LINE_START).build());
- final JRadioButton fixedPrecision = new JRadioButton("Fixed Precision");
+ final JRadioButton fixedPrecision = new JRadioButton(
+ "Fixed Precision");
fixedPrecision.setSelected(true);
- fixedPrecision.addActionListener(e -> this.presenter.setRoundingType(RoundingType.SIGNIFICANT_DIGITS));
+ fixedPrecision.addActionListener(e -> this.presenter
+ .setRoundingType(RoundingType.SIGNIFICANT_DIGITS));
roundingRuleButtons.add(fixedPrecision);
- roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
+ roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
- final JRadioButton fixedDecimals = new JRadioButton("Fixed Decimal Places");
- fixedDecimals.addActionListener(e -> this.presenter.setRoundingType(RoundingType.DECIMAL_PLACES));
+ final JRadioButton fixedDecimals = new JRadioButton(
+ "Fixed Decimal Places");
+ fixedDecimals.addActionListener(e -> this.presenter
+ .setRoundingType(RoundingType.DECIMAL_PLACES));
roundingRuleButtons.add(fixedDecimals);
- roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2).setAnchor(GridBagConstraints.LINE_START).build());
+ roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2)
+ .setAnchor(GridBagConstraints.LINE_START).build());
- final JRadioButton relativePrecision = new JRadioButton("Scientific Precision");
+ final JRadioButton relativePrecision = new JRadioButton(
+ "Scientific Precision");
relativePrecision.setEnabled(false);
- relativePrecision.addActionListener(e -> this.presenter.setRoundingType(RoundingType.SCIENTIFIC));
+ relativePrecision.addActionListener(e -> this.presenter
+ .setRoundingType(RoundingType.SCIENTIFIC));
roundingRuleButtons.add(relativePrecision);
- roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3).setAnchor(GridBagConstraints.LINE_START).build());
+ roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3)
+ .setAnchor(GridBagConstraints.LINE_START).build());
final JLabel sliderLabel = new JLabel("Precision:");
- roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4).setAnchor(GridBagConstraints.LINE_START).build());
+ roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4)
+ .setAnchor(GridBagConstraints.LINE_START).build());
final JSlider sigDigSlider = new JSlider(0, 12);
- roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5).setAnchor(GridBagConstraints.LINE_START).build());
-
+ roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
sigDigSlider.setMajorTickSpacing(4);
sigDigSlider.setMinorTickSpacing(1);
sigDigSlider.setSnapToTicks(true);
sigDigSlider.setPaintTicks(true);
sigDigSlider.setPaintLabels(true);
-
- sigDigSlider.addChangeListener(
- e -> this.presenter.setPrecision(sigDigSlider.getValue()));
+
+ sigDigSlider.addChangeListener(e -> this.presenter
+ .setPrecision(sigDigSlider.getValue()));
}
{ // prefix repetition settings
final JPanel prefixRepetitionPanel = new JPanel();
settingsPanel.add(prefixRepetitionPanel);
- prefixRepetitionPanel.setBorder(new TitledBorder("Prefix Repetition Settings"));
+ prefixRepetitionPanel.setBorder(
+ new TitledBorder("Prefix Repetition Settings"));
prefixRepetitionPanel.setLayout(new GridBagLayout());
// prefix rules
final ButtonGroup prefixRuleButtons = new ButtonGroup();
- final JRadioButton noRepetition = new JRadioButton("No Repetition");
+ final JRadioButton noRepetition = new JRadioButton(
+ "No Repetition");
noRepetition.setEnabled(false);
prefixRuleButtons.add(noRepetition);
- prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
+ prefixRepetitionPanel.add(noRepetition,
+ new GridBagBuilder(0, 0)
+ .setAnchor(GridBagConstraints.LINE_START)
+ .build());
- final JRadioButton noRestriction = new JRadioButton("No Restriction");
+ final JRadioButton noRestriction = new JRadioButton(
+ "No Restriction");
noRestriction.setSelected(true);
noRestriction.setEnabled(false);
prefixRuleButtons.add(noRestriction);
- prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
+ prefixRepetitionPanel.add(noRestriction,
+ new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START)
+ .build());
- final JRadioButton customRepetition = new JRadioButton("Custom Repetition Rule");
+ final JRadioButton customRepetition = new JRadioButton(
+ "Custom Repetition Rule");
customRepetition.setEnabled(false);
prefixRuleButtons.add(customRepetition);
- prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2).setAnchor(GridBagConstraints.LINE_START).build());
+ prefixRepetitionPanel.add(customRepetition,
+ new GridBagBuilder(0, 2)
+ .setAnchor(GridBagConstraints.LINE_START)
+ .build());
}
{ // search settings
@@ -930,46 +1065,59 @@ final class UnitConverterGUI {
// searching rules
final ButtonGroup searchRuleButtons = new ButtonGroup();
- final JRadioButton noPrefixes = new JRadioButton("Never Include Prefixed Units");
+ final JRadioButton noPrefixes = new JRadioButton(
+ "Never Include Prefixed Units");
noPrefixes.setEnabled(false);
searchRuleButtons.add(noPrefixes);
- searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
+ searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0)
+ .setAnchor(GridBagConstraints.LINE_START).build());
- final JRadioButton fixedPrefixes = new JRadioButton("Include Some Prefixes");
+ final JRadioButton fixedPrefixes = new JRadioButton(
+ "Include Some Prefixes");
fixedPrefixes.setEnabled(false);
searchRuleButtons.add(fixedPrefixes);
- searchingPanel.add(fixedPrefixes, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
+ searchingPanel.add(fixedPrefixes, new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
- final JRadioButton explicitPrefixes = new JRadioButton("Include Explicit Prefixes");
+ final JRadioButton explicitPrefixes = new JRadioButton(
+ "Include Explicit Prefixes");
explicitPrefixes.setEnabled(false);
searchRuleButtons.add(explicitPrefixes);
- searchingPanel.add(explicitPrefixes, new GridBagBuilder(0, 2).setAnchor(GridBagConstraints.LINE_START).build());
+ searchingPanel.add(explicitPrefixes, new GridBagBuilder(0, 2)
+ .setAnchor(GridBagConstraints.LINE_START).build());
- final JRadioButton alwaysInclude = new JRadioButton("Include All Single Prefixes");
+ final JRadioButton alwaysInclude = new JRadioButton(
+ "Include All Single Prefixes");
alwaysInclude.setEnabled(false);
searchRuleButtons.add(alwaysInclude);
- searchingPanel.add(alwaysInclude, new GridBagBuilder(0, 3).setAnchor(GridBagConstraints.LINE_START).build());
+ searchingPanel.add(alwaysInclude, new GridBagBuilder(0, 3)
+ .setAnchor(GridBagConstraints.LINE_START).build());
}
{ // miscellaneous settings
final JPanel miscPanel = new JPanel();
settingsPanel.add(miscPanel);
- miscPanel.setBorder(new TitledBorder("Miscellaneous Settings"));
+ miscPanel
+ .setBorder(new TitledBorder("Miscellaneous Settings"));
miscPanel.setLayout(new GridBagLayout());
- final JCheckBox showAllVariations = new JCheckBox("Show Symbols in \"Convert Units\"");
+ final JCheckBox showAllVariations = new JCheckBox(
+ "Show Symbols in \"Convert Units\"");
showAllVariations.setSelected(true);
showAllVariations.setEnabled(false);
- miscPanel.add(showAllVariations, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
+ miscPanel.add(showAllVariations, new GridBagBuilder(0, 0)
+ .setAnchor(GridBagConstraints.LINE_START).build());
- final JButton unitFileButton = new JButton("Manage Unit Data Files");
+ final JButton unitFileButton = new JButton(
+ "Manage Unit Data Files");
unitFileButton.setEnabled(false);
- miscPanel.add(unitFileButton, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
+ miscPanel.add(unitFileButton, new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
}
}
}
}
-
+
/**
* Sets the text in the output of the dimension-based converter.
*
@@ -980,7 +1128,7 @@ final class UnitConverterGUI {
public void setDimensionConverterOutputText(final String text) {
this.dimensionBasedOutput.setText(text);
}
-
+
/**
* Sets the text in the output of the conversion panel.
*
@@ -991,7 +1139,7 @@ final class UnitConverterGUI {
public void setExpressionConverterOutputText(final String text) {
this.output.setText(text);
}
-
+
/**
* Sets the text of the prefix text box in the prefix viewer.
*
@@ -1002,7 +1150,7 @@ final class UnitConverterGUI {
public void setPrefixTextBoxText(final String text) {
this.prefixTextBox.setText(text);
}
-
+
/**
* Sets the text of the unit text box in the unit viewer.
*
@@ -1013,7 +1161,7 @@ final class UnitConverterGUI {
public void setUnitTextBoxText(final String text) {
this.unitTextBox.setText(text);
}
-
+
/**
* Shows an error dialog.
*
@@ -1023,10 +1171,11 @@ final class UnitConverterGUI {
* @since v0.1.0
*/
public void showErrorDialog(final String title, final String message) {
- JOptionPane.showMessageDialog(this.frame, message, title, JOptionPane.ERROR_MESSAGE);
+ JOptionPane.showMessageDialog(this.frame, message, title,
+ JOptionPane.ERROR_MESSAGE);
}
}
-
+
public static void main(final String[] args) {
new View().init();
}
diff --git a/src/org/unitConverter/unit/LinearUnitValue.java b/src/org/unitConverter/unit/LinearUnitValue.java
index 74b0400..7096738 100644
--- a/src/org/unitConverter/unit/LinearUnitValue.java
+++ b/src/org/unitConverter/unit/LinearUnitValue.java
@@ -20,6 +20,8 @@ import org.unitConverter.math.DecimalComparison;
* @since 2020-07-26
*/
public final class LinearUnitValue {
+ public static final LinearUnitValue ONE = getExact(SI.ONE, 1);
+
/**
* Gets an exact {@code LinearUnitValue}
*
@@ -82,6 +84,15 @@ public final class LinearUnitValue {
}
/**
+ * @return this value as a {@code UnitValue}. All uncertainty information is
+ * removed from the returned value.
+ * @since 2020-08-04
+ */
+ public final UnitValue asUnitValue() {
+ return UnitValue.of(this.unit, this.value);
+ }
+
+ /**
* @param other a {@code LinearUnit}
* @return true iff this value can be represented with {@code other}.
* @since 2020-07-26
@@ -135,6 +146,7 @@ public final class LinearUnitValue {
* km) returns true.
*
* @since 2020-07-26
+ * @see #equals(Object, boolean)
*/
@Override
public boolean equals(final Object obj) {
diff --git a/src/org/unitConverter/unit/UnitDatabase.java b/src/org/unitConverter/unit/UnitDatabase.java
index 507266d..0246630 100644
--- a/src/org/unitConverter/unit/UnitDatabase.java
+++ b/src/org/unitConverter/unit/UnitDatabase.java
@@ -27,6 +27,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -55,29 +56,33 @@ public final class UnitDatabase {
/**
* A map for units that allows the use of prefixes.
* <p>
- * As this map implementation is intended to be used as a sort of "augmented view" of a unit and prefix map, it is
- * unmodifiable but instead reflects the changes to the maps passed into it. Do not edit this map, instead edit the
- * maps that were passed in during construction.
+ * As this map implementation is intended to be used as a sort of "augmented
+ * view" of a unit and prefix map, it is unmodifiable but instead reflects
+ * the changes to the maps passed into it. Do not edit this map, instead edit
+ * the maps that were passed in during construction.
* </p>
* <p>
* The rules for applying prefixes onto units are the following:
* <ul>
* <li>Prefixes can only be applied to linear units.</li>
- * <li>Before attempting to search for prefixes in a unit name, this map will first search for a unit name. So, if
- * there are two units, "B" and "AB", and a prefix "A", this map will favour the unit "AB" over the unit "B" with
- * the prefix "A", even though they have the same string.</li>
- * <li>Longer prefixes are preferred to shorter prefixes. So, if you have units "BC" and "C", and prefixes "AB" and
- * "A", inputting "ABC" will return the unit "C" with the prefix "AB", not "BC" with the prefix "A".</li>
+ * <li>Before attempting to search for prefixes in a unit name, this map will
+ * first search for a unit name. So, if there are two units, "B" and "AB",
+ * and a prefix "A", this map will favour the unit "AB" over the unit "B"
+ * with the prefix "A", even though they have the same string.</li>
+ * <li>Longer prefixes are preferred to shorter prefixes. So, if you have
+ * units "BC" and "C", and prefixes "AB" and "A", inputting "ABC" will return
+ * the unit "C" with the prefix "AB", not "BC" with the prefix "A".</li>
* </ul>
* </p>
* <p>
- * This map is infinite in size if there is at least one unit and at least one prefix. If it is infinite, some
- * operations that only work with finite collections, like converting name/entry sets to arrays, will throw an
+ * This map is infinite in size if there is at least one unit and at least
+ * one prefix. If it is infinite, some operations that only work with finite
+ * collections, like converting name/entry sets to arrays, will throw an
* {@code IllegalStateException}.
* </p>
* <p>
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), {@link #containsValue} and {@link #values()}
- * currently ignore prefixes.
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega),
+ * {@link #containsValue} and {@link #values()} currently ignore prefixes.
* </p>
*
* @author Adrien Hopkins
@@ -89,16 +94,19 @@ public final class UnitDatabase {
* The class used for entry sets.
*
* <p>
- * If the map that created this set is infinite in size (has at least one unit and at least one prefix), this
- * set is infinite as well. If this set is infinite in size, {@link #toArray} will fail with a
- * {@code IllegalStateException} instead of creating an infinite-sized array.
+ * If the map that created this set is infinite in size (has at least one
+ * unit and at least one prefix), this set is infinite as well. If this
+ * set is infinite in size, {@link #toArray} will fail with a
+ * {@code IllegalStateException} instead of creating an infinite-sized
+ * array.
* </p>
*
* @author Adrien Hopkins
* @since 2019-04-13
* @since v0.2.0
*/
- private static final class PrefixedUnitEntrySet extends AbstractSet<Map.Entry<String, Unit>> {
+ private static final class PrefixedUnitEntrySet
+ extends AbstractSet<Map.Entry<String, Unit>> {
/**
* The entry for this set.
*
@@ -106,17 +114,16 @@ public final class UnitDatabase {
* @since 2019-04-14
* @since v0.2.0
*/
- private static final class PrefixedUnitEntry implements Entry<String, Unit> {
+ private static final class PrefixedUnitEntry
+ implements Entry<String, Unit> {
private final String key;
private final Unit value;
-
+
/**
* Creates the {@code PrefixedUnitEntry}.
*
- * @param key
- * key
- * @param value
- * value
+ * @param key key
+ * @param value value
* @since 2019-04-14
* @since v0.2.0
*/
@@ -124,7 +131,7 @@ public final class UnitDatabase {
this.key = key;
this.value = value;
}
-
+
/**
* @since 2019-05-03
*/
@@ -136,34 +143,38 @@ public final class UnitDatabase {
return Objects.equals(this.getKey(), other.getKey())
&& Objects.equals(this.getValue(), other.getValue());
}
-
+
@Override
public String getKey() {
return this.key;
}
-
+
@Override
public Unit getValue() {
return this.value;
}
-
+
/**
* @since 2019-05-03
*/
@Override
public int hashCode() {
return (this.getKey() == null ? 0 : this.getKey().hashCode())
- ^ (this.getValue() == null ? 0 : this.getValue().hashCode());
+ ^ (this.getValue() == null ? 0
+ : this.getValue().hashCode());
}
-
+
@Override
public Unit setValue(final Unit value) {
- throw new UnsupportedOperationException("Cannot set value in an immutable entry");
+ throw new UnsupportedOperationException(
+ "Cannot set value in an immutable entry");
}
-
+
/**
- * Returns a string representation of the entry. The format of the string is the string representation
- * of the key, then the equals ({@code =}) character, then the string representation of the value.
+ * Returns a string representation of the entry. The format of the
+ * string is the string representation of the key, then the equals
+ * ({@code =}) character, then the string representation of the
+ * value.
*
* @since 2019-05-03
*/
@@ -172,27 +183,30 @@ public final class UnitDatabase {
return this.getKey() + "=" + this.getValue();
}
}
-
+
/**
- * An iterator that iterates over the units of a {@code PrefixedUnitNameSet}.
+ * An iterator that iterates over the units of a
+ * {@code PrefixedUnitNameSet}.
*
* @author Adrien Hopkins
* @since 2019-04-14
* @since v0.2.0
*/
- private static final class PrefixedUnitEntryIterator implements Iterator<Entry<String, Unit>> {
+ private static final class PrefixedUnitEntryIterator
+ implements Iterator<Entry<String, Unit>> {
// position in the unit list
private int unitNamePosition = 0;
// the indices of the prefixes attached to the current unit
private final List<Integer> prefixCoordinates = new ArrayList<>();
-
+
// values from the unit entry set
private final Map<String, Unit> map;
private transient final List<String> unitNames;
private transient final List<String> prefixNames;
-
+
/**
- * Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
+ * Creates the
+ * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
*
* @since 2019-04-14
* @since v0.2.0
@@ -202,7 +216,7 @@ public final class UnitDatabase {
this.unitNames = new ArrayList<>(map.units.keySet());
this.prefixNames = new ArrayList<>(map.prefixes.keySet());
}
-
+
/**
* @return current unit name
* @since 2019-04-14
@@ -214,10 +228,10 @@ public final class UnitDatabase {
unitName.append(this.prefixNames.get(i));
}
unitName.append(this.unitNames.get(this.unitNamePosition));
-
+
return unitName.toString();
}
-
+
@Override
public boolean hasNext() {
if (this.unitNames.isEmpty())
@@ -229,7 +243,7 @@ public final class UnitDatabase {
return true;
}
}
-
+
/**
* Changes this iterator's position to the next available one.
*
@@ -238,127 +252,142 @@ public final class UnitDatabase {
*/
private void incrementPosition() {
this.unitNamePosition++;
-
+
if (this.unitNamePosition >= this.unitNames.size()) {
// we have used all of our units, go to a different prefix
this.unitNamePosition = 0;
-
+
// if the prefix coordinates are empty, then set it to [0]
if (this.prefixCoordinates.isEmpty()) {
this.prefixCoordinates.add(0, 0);
} else {
// get the prefix coordinate to increment, then increment
int i = this.prefixCoordinates.size() - 1;
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
-
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
+
// fix any carrying errors
- while (i >= 0 && this.prefixCoordinates.get(i) >= this.prefixNames.size()) {
+ while (i >= 0 && this.prefixCoordinates
+ .get(i) >= this.prefixNames.size()) {
// carry over
- this.prefixCoordinates.set(i--, 0); // null and decrement at the same time
-
+ this.prefixCoordinates.set(i--, 0); // null and
+ // decrement at the
+ // same time
+
if (i < 0) { // we need to add a new coordinate
this.prefixCoordinates.add(0, 0);
} else { // increment an existing one
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
}
}
}
}
}
-
+
@Override
public Entry<String, Unit> next() {
// get next element
final Entry<String, Unit> nextEntry = this.peek();
-
+
// iterate to next position
this.incrementPosition();
-
+
return nextEntry;
}
-
+
/**
- * @return the next element in the iterator, without iterating over it
+ * @return the next element in the iterator, without iterating over
+ * it
* @since 2019-05-03
*/
private Entry<String, Unit> peek() {
if (!this.hasNext())
throw new NoSuchElementException("No units left!");
-
+
// if I have prefixes, ensure I'm not using a nonlinear unit
- // since all of the unprefixed stuff is done, just remove nonlinear units
+ // since all of the unprefixed stuff is done, just remove
+ // nonlinear units
if (!this.prefixCoordinates.isEmpty()) {
while (this.unitNamePosition < this.unitNames.size()
- && !(this.map.get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
+ && !(this.map.get(this.unitNames.get(
+ this.unitNamePosition)) instanceof LinearUnit)) {
this.unitNames.remove(this.unitNamePosition);
}
}
-
+
final String nextName = this.getCurrentUnitName();
-
+
return new PrefixedUnitEntry(nextName, this.map.get(nextName));
}
-
+
/**
- * Returns a string representation of the object. The exact details of the representation are
- * unspecified and subject to change.
+ * Returns a string representation of the object. The exact details
+ * of the representation are unspecified and subject to change.
*
* @since 2019-05-03
*/
@Override
public String toString() {
- return String.format("Iterator iterating over name-unit entries; next value is \"%s\"",
+ return String.format(
+ "Iterator iterating over name-unit entries; next value is \"%s\"",
this.peek());
}
}
-
+
// the map that created this set
private final PrefixedUnitMap map;
-
+
/**
* Creates the {@code PrefixedUnitNameSet}.
*
- * @param map
- * map that created this set
+ * @param map map that created this set
* @since 2019-04-13
* @since v0.2.0
*/
public PrefixedUnitEntrySet(final PrefixedUnitMap map) {
this.map = map;
}
-
+
@Override
public boolean add(final Map.Entry<String, Unit> e) {
- throw new UnsupportedOperationException("Cannot add to an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
}
-
+
@Override
- public boolean addAll(final Collection<? extends Map.Entry<String, Unit>> c) {
- throw new UnsupportedOperationException("Cannot add to an immutable set");
+ public boolean addAll(
+ final Collection<? extends Map.Entry<String, Unit>> c) {
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
}
-
+
@Override
public void clear() {
- throw new UnsupportedOperationException("Cannot clear an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot clear an immutable set");
}
-
+
@Override
public boolean contains(final Object o) {
// get the entry
final Entry<String, Unit> entry;
-
+
try {
- // This is OK because I'm in a try-catch block, catching the exact exception that would be thrown.
+ // This is OK because I'm in a try-catch block, catching the
+ // exact exception that would be thrown.
@SuppressWarnings("unchecked")
final Entry<String, Unit> tempEntry = (Entry<String, Unit>) o;
entry = tempEntry;
} catch (final ClassCastException e) {
- throw new IllegalArgumentException("Attempted to test for an entry using a non-entry.");
+ throw new IllegalArgumentException(
+ "Attempted to test for an entry using a non-entry.");
}
-
- return this.map.containsKey(entry.getKey()) && this.map.get(entry.getKey()).equals(entry.getValue());
+
+ return this.map.containsKey(entry.getKey())
+ && this.map.get(entry.getKey()).equals(entry.getValue());
}
-
+
@Override
public boolean containsAll(final Collection<?> c) {
for (final Object o : c)
@@ -366,37 +395,42 @@ public final class UnitDatabase {
return false;
return true;
}
-
+
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
-
+
@Override
public Iterator<Entry<String, Unit>> iterator() {
return new PrefixedUnitEntryIterator(this.map);
}
-
+
@Override
public boolean remove(final Object o) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeAll(final Collection<?> c) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
- public boolean removeIf(final Predicate<? super Entry<String, Unit>> filter) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ public boolean removeIf(
+ final Predicate<? super Entry<String, Unit>> filter) {
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
public boolean retainAll(final Collection<?> c) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
public int size() {
if (this.map.units.isEmpty())
@@ -409,10 +443,9 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
/**
- * @throws IllegalStateException
- * if the set is infinite in size
+ * @throws IllegalStateException if the set is infinite in size
*/
@Override
public Object[] toArray() {
@@ -420,12 +453,12 @@ public final class UnitDatabase {
return super.toArray();
else
// infinite set
- throw new IllegalStateException("Cannot make an infinite set into an array.");
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
}
-
+
/**
- * @throws IllegalStateException
- * if the set is infinite in size
+ * @throws IllegalStateException if the set is infinite in size
*/
@Override
public <T> T[] toArray(final T[] a) {
@@ -433,53 +466,61 @@ public final class UnitDatabase {
return super.toArray(a);
else
// infinite set
- throw new IllegalStateException("Cannot make an infinite set into an array.");
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
}
-
+
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toString();
else
- return String.format("Infinite set of name-unit entries created from units %s and prefixes %s",
+ return String.format(
+ "Infinite set of name-unit entries created from units %s and prefixes %s",
this.map.units, this.map.prefixes);
}
}
-
+
/**
* The class used for unit name sets.
*
* <p>
- * If the map that created this set is infinite in size (has at least one unit and at least one prefix), this
- * set is infinite as well. If this set is infinite in size, {@link #toArray} will fail with a
- * {@code IllegalStateException} instead of creating an infinite-sized array.
+ * If the map that created this set is infinite in size (has at least one
+ * unit and at least one prefix), this set is infinite as well. If this
+ * set is infinite in size, {@link #toArray} will fail with a
+ * {@code IllegalStateException} instead of creating an infinite-sized
+ * array.
* </p>
*
* @author Adrien Hopkins
* @since 2019-04-13
* @since v0.2.0
*/
- private static final class PrefixedUnitNameSet extends AbstractSet<String> {
+ private static final class PrefixedUnitNameSet
+ extends AbstractSet<String> {
/**
- * An iterator that iterates over the units of a {@code PrefixedUnitNameSet}.
+ * An iterator that iterates over the units of a
+ * {@code PrefixedUnitNameSet}.
*
* @author Adrien Hopkins
* @since 2019-04-14
* @since v0.2.0
*/
- private static final class PrefixedUnitNameIterator implements Iterator<String> {
+ private static final class PrefixedUnitNameIterator
+ implements Iterator<String> {
// position in the unit list
private int unitNamePosition = 0;
// the indices of the prefixes attached to the current unit
private final List<Integer> prefixCoordinates = new ArrayList<>();
-
+
// values from the unit name set
private final Map<String, Unit> map;
private transient final List<String> unitNames;
private transient final List<String> prefixNames;
-
+
/**
- * Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
+ * Creates the
+ * {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
*
* @since 2019-04-14
* @since v0.2.0
@@ -489,7 +530,7 @@ public final class UnitDatabase {
this.unitNames = new ArrayList<>(map.units.keySet());
this.prefixNames = new ArrayList<>(map.prefixes.keySet());
}
-
+
/**
* @return current unit name
* @since 2019-04-14
@@ -501,10 +542,10 @@ public final class UnitDatabase {
unitName.append(this.prefixNames.get(i));
}
unitName.append(this.unitNames.get(this.unitNamePosition));
-
+
return unitName.toString();
}
-
+
@Override
public boolean hasNext() {
if (this.unitNames.isEmpty())
@@ -516,7 +557,7 @@ public final class UnitDatabase {
return true;
}
}
-
+
/**
* Changes this iterator's position to the next available one.
*
@@ -525,109 +566,121 @@ public final class UnitDatabase {
*/
private void incrementPosition() {
this.unitNamePosition++;
-
+
if (this.unitNamePosition >= this.unitNames.size()) {
// we have used all of our units, go to a different prefix
this.unitNamePosition = 0;
-
+
// if the prefix coordinates are empty, then set it to [0]
if (this.prefixCoordinates.isEmpty()) {
this.prefixCoordinates.add(0, 0);
} else {
// get the prefix coordinate to increment, then increment
int i = this.prefixCoordinates.size() - 1;
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
-
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
+
// fix any carrying errors
- while (i >= 0 && this.prefixCoordinates.get(i) >= this.prefixNames.size()) {
+ while (i >= 0 && this.prefixCoordinates
+ .get(i) >= this.prefixNames.size()) {
// carry over
- this.prefixCoordinates.set(i--, 0); // null and decrement at the same time
-
+ this.prefixCoordinates.set(i--, 0); // null and
+ // decrement at the
+ // same time
+
if (i < 0) { // we need to add a new coordinate
this.prefixCoordinates.add(0, 0);
} else { // increment an existing one
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
+ this.prefixCoordinates.set(i,
+ this.prefixCoordinates.get(i) + 1);
}
}
}
}
}
-
+
@Override
public String next() {
final String nextName = this.peek();
-
+
this.incrementPosition();
-
+
return nextName;
}
-
+
/**
- * @return the next element in the iterator, without iterating over it
+ * @return the next element in the iterator, without iterating over
+ * it
* @since 2019-05-03
*/
private String peek() {
if (!this.hasNext())
throw new NoSuchElementException("No units left!");
// if I have prefixes, ensure I'm not using a nonlinear unit
- // since all of the unprefixed stuff is done, just remove nonlinear units
+ // since all of the unprefixed stuff is done, just remove
+ // nonlinear units
if (!this.prefixCoordinates.isEmpty()) {
while (this.unitNamePosition < this.unitNames.size()
- && !(this.map.get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
+ && !(this.map.get(this.unitNames.get(
+ this.unitNamePosition)) instanceof LinearUnit)) {
this.unitNames.remove(this.unitNamePosition);
}
}
-
+
return this.getCurrentUnitName();
}
-
+
/**
- * Returns a string representation of the object. The exact details of the representation are
- * unspecified and subject to change.
+ * Returns a string representation of the object. The exact details
+ * of the representation are unspecified and subject to change.
*
* @since 2019-05-03
*/
@Override
public String toString() {
- return String.format("Iterator iterating over unit names; next value is \"%s\"", this.peek());
+ return String.format(
+ "Iterator iterating over unit names; next value is \"%s\"",
+ this.peek());
}
}
-
+
// the map that created this set
private final PrefixedUnitMap map;
-
+
/**
* Creates the {@code PrefixedUnitNameSet}.
*
- * @param map
- * map that created this set
+ * @param map map that created this set
* @since 2019-04-13
* @since v0.2.0
*/
public PrefixedUnitNameSet(final PrefixedUnitMap map) {
this.map = map;
}
-
+
@Override
public boolean add(final String e) {
- throw new UnsupportedOperationException("Cannot add to an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
}
-
+
@Override
public boolean addAll(final Collection<? extends String> c) {
- throw new UnsupportedOperationException("Cannot add to an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot add to an immutable set");
}
-
+
@Override
public void clear() {
- throw new UnsupportedOperationException("Cannot clear an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot clear an immutable set");
}
-
+
@Override
public boolean contains(final Object o) {
return this.map.containsKey(o);
}
-
+
@Override
public boolean containsAll(final Collection<?> c) {
for (final Object o : c)
@@ -635,37 +688,41 @@ public final class UnitDatabase {
return false;
return true;
}
-
+
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
-
+
@Override
public Iterator<String> iterator() {
return new PrefixedUnitNameIterator(this.map);
}
-
+
@Override
public boolean remove(final Object o) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeAll(final Collection<?> c) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeIf(final Predicate<? super String> filter) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
public boolean retainAll(final Collection<?> c) {
- throw new UnsupportedOperationException("Cannot remove from an immutable set");
+ throw new UnsupportedOperationException(
+ "Cannot remove from an immutable set");
}
-
+
@Override
public int size() {
if (this.map.units.isEmpty())
@@ -678,10 +735,9 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
/**
- * @throws IllegalStateException
- * if the set is infinite in size
+ * @throws IllegalStateException if the set is infinite in size
*/
@Override
public Object[] toArray() {
@@ -689,13 +745,13 @@ public final class UnitDatabase {
return super.toArray();
else
// infinite set
- throw new IllegalStateException("Cannot make an infinite set into an array.");
-
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
+
}
-
+
/**
- * @throws IllegalStateException
- * if the set is infinite in size
+ * @throws IllegalStateException if the set is infinite in size
*/
@Override
public <T> T[] toArray(final T[] a) {
@@ -703,19 +759,21 @@ public final class UnitDatabase {
return super.toArray(a);
else
// infinite set
- throw new IllegalStateException("Cannot make an infinite set into an array.");
+ throw new IllegalStateException(
+ "Cannot make an infinite set into an array.");
}
-
+
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toString();
else
- return String.format("Infinite set of name-unit entries created from units %s and prefixes %s",
+ return String.format(
+ "Infinite set of name-unit entries created from units %s and prefixes %s",
this.map.units, this.map.prefixes);
}
}
-
+
/**
* The units stored in this collection, without prefixes.
*
@@ -723,7 +781,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, Unit> units;
-
+
/**
* The available prefixes for use.
*
@@ -731,95 +789,106 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, UnitPrefix> prefixes;
-
+
// caches
private transient Collection<Unit> values = null;
private transient Set<String> keySet = null;
private transient Set<Entry<String, Unit>> entrySet = null;
-
+
/**
* Creates the {@code PrefixedUnitMap}.
*
- * @param units
- * map mapping unit names to units
- * @param prefixes
- * map mapping prefix names to prefixes
+ * @param units map mapping unit names to units
+ * @param prefixes map mapping prefix names to prefixes
* @since 2019-04-13
* @since v0.2.0
*/
- public PrefixedUnitMap(final Map<String, Unit> units, final Map<String, UnitPrefix> prefixes) {
- // I am making unmodifiable maps to ensure I don't accidentally make changes.
+ public PrefixedUnitMap(final Map<String, Unit> units,
+ final Map<String, UnitPrefix> prefixes) {
+ // I am making unmodifiable maps to ensure I don't accidentally make
+ // changes.
this.units = Collections.unmodifiableMap(units);
this.prefixes = Collections.unmodifiableMap(prefixes);
}
-
+
@Override
public void clear() {
- throw new UnsupportedOperationException("Cannot clear an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot clear an immutable map");
}
-
+
@Override
public Unit compute(final String key,
final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
- throw new UnsupportedOperationException("Cannot edit an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot edit an immutable map");
}
-
+
@Override
- public Unit computeIfAbsent(final String key, final Function<? super String, ? extends Unit> mappingFunction) {
- throw new UnsupportedOperationException("Cannot edit an immutable map");
+ public Unit computeIfAbsent(final String key,
+ final Function<? super String, ? extends Unit> mappingFunction) {
+ throw new UnsupportedOperationException(
+ "Cannot edit an immutable map");
}
-
+
@Override
public Unit computeIfPresent(final String key,
final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
- throw new UnsupportedOperationException("Cannot edit an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot edit an immutable map");
}
-
+
@Override
public boolean containsKey(final Object key) {
// First, test if there is a unit with the key
if (this.units.containsKey(key))
return true;
-
+
// Next, try to cast it to String
if (!(key instanceof String))
- throw new IllegalArgumentException("Attempted to test for a unit using a non-string name.");
+ throw new IllegalArgumentException(
+ "Attempted to test for a unit using a non-string name.");
final String unitName = (String) key;
-
+
// Then, look for the longest prefix that is attached to a valid unit
String longestPrefix = null;
int longestLength = 0;
-
+
for (final String prefixName : this.prefixes.keySet()) {
// a prefix name is valid if:
// - it is prefixed (i.e. the unit name starts with it)
- // - it is longer than the existing largest prefix (since I am looking for the longest valid prefix)
+ // - it is longer than the existing largest prefix (since I am
+ // looking for the longest valid prefix)
// - the part after the prefix is a valid unit name
- // - the unit described that name is a linear unit (since only linear units can have prefixes)
- if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) {
+ // - the unit described that name is a linear unit (since only
+ // linear units can have prefixes)
+ if (unitName.startsWith(prefixName)
+ && prefixName.length() > longestLength) {
final String rest = unitName.substring(prefixName.length());
- if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
+ if (this.containsKey(rest)
+ && this.get(rest) instanceof LinearUnit) {
longestPrefix = prefixName;
longestLength = prefixName.length();
}
}
}
-
+
return longestPrefix != null;
}
-
+
/**
* {@inheritDoc}
*
* <p>
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method only tests for prefixless units.
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), this
+ * method only tests for prefixless units.
* </p>
*/
@Override
public boolean containsValue(final Object value) {
return this.units.containsValue(value);
}
-
+
@Override
public Set<Entry<String, Unit>> entrySet() {
if (this.entrySet == null) {
@@ -827,56 +896,62 @@ public final class UnitDatabase {
}
return this.entrySet;
}
-
+
@Override
public Unit get(final Object key) {
// First, test if there is a unit with the key
if (this.units.containsKey(key))
return this.units.get(key);
-
+
// Next, try to cast it to String
if (!(key instanceof String))
- throw new IllegalArgumentException("Attempted to obtain a unit using a non-string name.");
+ throw new IllegalArgumentException(
+ "Attempted to obtain a unit using a non-string name.");
final String unitName = (String) key;
-
+
// Then, look for the longest prefix that is attached to a valid unit
String longestPrefix = null;
int longestLength = 0;
-
+
for (final String prefixName : this.prefixes.keySet()) {
// a prefix name is valid if:
// - it is prefixed (i.e. the unit name starts with it)
- // - it is longer than the existing largest prefix (since I am looking for the longest valid prefix)
+ // - it is longer than the existing largest prefix (since I am
+ // looking for the longest valid prefix)
// - the part after the prefix is a valid unit name
- // - the unit described that name is a linear unit (since only linear units can have prefixes)
- if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) {
+ // - the unit described that name is a linear unit (since only
+ // linear units can have prefixes)
+ if (unitName.startsWith(prefixName)
+ && prefixName.length() > longestLength) {
final String rest = unitName.substring(prefixName.length());
- if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
+ if (this.containsKey(rest)
+ && this.get(rest) instanceof LinearUnit) {
longestPrefix = prefixName;
longestLength = prefixName.length();
}
}
}
-
+
// if none found, returns null
if (longestPrefix == null)
return null;
else {
// get necessary data
final String rest = unitName.substring(longestLength);
- // this cast will not fail because I verified that it would work before selecting this prefix
+ // this cast will not fail because I verified that it would work
+ // before selecting this prefix
final LinearUnit unit = (LinearUnit) this.get(rest);
final UnitPrefix prefix = this.prefixes.get(longestPrefix);
-
+
return unit.withPrefix(prefix);
}
}
-
+
@Override
public boolean isEmpty() {
return this.units.isEmpty();
}
-
+
@Override
public Set<String> keySet() {
if (this.keySet == null) {
@@ -884,53 +959,64 @@ public final class UnitDatabase {
}
return this.keySet;
}
-
+
@Override
public Unit merge(final String key, final Unit value,
final BiFunction<? super Unit, ? super Unit, ? extends Unit> remappingFunction) {
- throw new UnsupportedOperationException("Cannot merge into an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot merge into an immutable map");
}
-
+
@Override
public Unit put(final String key, final Unit value) {
- throw new UnsupportedOperationException("Cannot add entries to an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot add entries to an immutable map");
}
-
+
@Override
public void putAll(final Map<? extends String, ? extends Unit> m) {
- throw new UnsupportedOperationException("Cannot add entries to an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot add entries to an immutable map");
}
-
+
@Override
public Unit putIfAbsent(final String key, final Unit value) {
- throw new UnsupportedOperationException("Cannot add entries to an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot add entries to an immutable map");
}
-
+
@Override
public Unit remove(final Object key) {
- throw new UnsupportedOperationException("Cannot remove entries from an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot remove entries from an immutable map");
}
-
+
@Override
public boolean remove(final Object key, final Object value) {
- throw new UnsupportedOperationException("Cannot remove entries from an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot remove entries from an immutable map");
}
-
+
@Override
public Unit replace(final String key, final Unit value) {
- throw new UnsupportedOperationException("Cannot replace entries in an immutable map");
+ throw new UnsupportedOperationException(
+ "Cannot replace entries in an immutable map");
}
-
+
@Override
- public boolean replace(final String key, final Unit oldValue, final Unit newValue) {
- throw new UnsupportedOperationException("Cannot replace entries in an immutable map");
+ public boolean replace(final String key, final Unit oldValue,
+ final Unit newValue) {
+ throw new UnsupportedOperationException(
+ "Cannot replace entries in an immutable map");
}
-
+
@Override
- public void replaceAll(final BiFunction<? super String, ? super Unit, ? extends Unit> function) {
- throw new UnsupportedOperationException("Cannot replace entries in an immutable map");
+ public void replaceAll(
+ final BiFunction<? super String, ? super Unit, ? extends Unit> function) {
+ throw new UnsupportedOperationException(
+ "Cannot replace entries in an immutable map");
}
-
+
@Override
public int size() {
if (this.units.isEmpty())
@@ -943,66 +1029,80 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
@Override
public String toString() {
if (this.units.isEmpty() || this.prefixes.isEmpty())
return super.toString();
else
- return String.format("Infinite map of name-unit entries created from units %s and prefixes %s",
+ return String.format(
+ "Infinite map of name-unit entries created from units %s and prefixes %s",
this.units, this.prefixes);
}
-
+
/**
* {@inheritDoc}
*
* <p>
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), this method ignores prefixes.
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), this
+ * method ignores prefixes.
* </p>
*/
@Override
public Collection<Unit> values() {
if (this.values == null) {
- this.values = Collections.unmodifiableCollection(this.units.values());
+ this.values = Collections
+ .unmodifiableCollection(this.units.values());
}
return this.values;
}
}
-
+
/**
* Replacements done to *all* expression types
*/
private static final Map<Pattern, String> EXPRESSION_REPLACEMENTS = new HashMap<>();
-
+
// add data to expression replacements
static {
- // place brackets around any expression of the form "number unit", with or without the space
+ // add spaces around operators
+ for (final String operator : Arrays.asList("\\*", "/", "\\^")) {
+ EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator),
+ " " + operator + " ");
+ }
+
+ // replace multiple spaces with a single space
+ EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " ");
+ // place brackets around any expression of the form "number unit", with or
+ // without the space
EXPRESSION_REPLACEMENTS.put(Pattern.compile("((?:-?[1-9]\\d*|0)" // integer
- + "(?:\\.\\d+(?:[eE]\\d+))?)" // optional decimal point with numbers after it
+ + "(?:\\.\\d+(?:[eE]\\d+))?)" // optional decimal point with numbers
+ // after it
+ "\\s*" // optional space(s)
+ "([a-zA-Z]+(?:\\^\\d+)?" // any string of letters
+ "(?:\\s+[a-zA-Z]+(?:\\^\\d+)?))" // optional other letters
- + "(?!-?\\d)" // no number directly afterwards (avoids matching "1e3")
+ + "(?!-?\\d)" // no number directly afterwards (avoids matching
+ // "1e3")
), "\\($1 $2\\)");
}
-
+
/**
* A regular expression that separates names and expressions in unit files.
*/
- private static final Pattern NAME_EXPRESSION = Pattern.compile("(\\S+)\\s+(\\S.*)");
-
+ private static final Pattern NAME_EXPRESSION = Pattern
+ .compile("(\\S+)\\s+(\\S.*)");
+
/**
* The exponent operator
*
- * @param base
- * base of exponentiation
- * @param exponentUnit
- * exponent
+ * @param base base of exponentiation
+ * @param exponentUnit exponent
* @return result
* @since 2019-04-10
* @since v0.2.0
*/
- private static final LinearUnit exponentiateUnits(final LinearUnit base, final LinearUnit exponentUnit) {
+ private static final LinearUnit exponentiateUnits(final LinearUnit base,
+ final LinearUnit exponentUnit) {
// exponent function - first check if o2 is a number,
if (exponentUnit.getBase().equals(SI.ONE.getBase())) {
// then check if it is an integer,
@@ -1012,12 +1112,39 @@ public final class UnitDatabase {
return base.toExponent((int) (exponent + 0.5));
else
// not an integer
- throw new UnsupportedOperationException("Decimal exponents are currently not supported.");
+ throw new UnsupportedOperationException(
+ "Decimal exponents are currently not supported.");
} else
// not a number
throw new IllegalArgumentException("Exponents must be numbers.");
}
-
+
+ /**
+ * The exponent operator
+ *
+ * @param base base of exponentiation
+ * @param exponentUnit exponent
+ * @return result
+ * @since 2020-08-04
+ */
+ private static final LinearUnitValue exponentiateUnitValues(
+ final LinearUnitValue base, final LinearUnitValue exponentValue) {
+ // exponent function - first check if o2 is a number,
+ if (exponentValue.canConvertTo(SI.ONE)) {
+ // then check if it is an integer,
+ final double exponent = exponentValue.getValue();
+ if (DecimalComparison.equals(exponent % 1, 0))
+ // then exponentiate
+ return base.toExponent((int) (exponent + 0.5));
+ else
+ // not an integer
+ throw new UnsupportedOperationException(
+ "Decimal exponents are currently not supported.");
+ } else
+ // not a number
+ throw new IllegalArgumentException("Exponents must be numbers.");
+ }
+
/**
* The units in this system, excluding prefixes.
*
@@ -1025,7 +1152,7 @@ public final class UnitDatabase {
* @since v0.1.0
*/
private final Map<String, Unit> prefixlessUnits;
-
+
/**
* The unit prefixes in this system.
*
@@ -1033,7 +1160,7 @@ public final class UnitDatabase {
* @since v0.1.0
*/
private final Map<String, UnitPrefix> prefixes;
-
+
/**
* The dimensions in this system.
*
@@ -1041,7 +1168,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, ObjectProduct<BaseDimension>> dimensions;
-
+
/**
* A map mapping strings to units (including prefixes)
*
@@ -1049,7 +1176,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, Unit> units;
-
+
/**
* A parser that can parse unit expressions.
*
@@ -1059,21 +1186,41 @@ public final class UnitDatabase {
private final ExpressionParser<LinearUnit> unitExpressionParser = new ExpressionParser.Builder<>(
this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
.addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
- .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1).addSpaceFunction("*")
+ .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
+ .addSpaceFunction("*")
.addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
- .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2).build();
-
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2)
+ .build();
+
+ /**
+ * A parser that can parse unit value expressions.
+ *
+ * @since 2020-08-04
+ */
+ private final ExpressionParser<LinearUnitValue> unitValueExpressionParser = new ExpressionParser.Builder<>(
+ this::getLinearUnitValue)
+ .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
+ .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
+ .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2)
+ .build();
+
/**
* A parser that can parse unit prefix expressions
*
* @since 2019-04-13
* @since v0.2.0
*/
- private final ExpressionParser<UnitPrefix> prefixExpressionParser = new ExpressionParser.Builder<>(this::getPrefix)
- .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0).addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
- .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()), 1).build();
-
+ private final ExpressionParser<UnitPrefix> prefixExpressionParser = new ExpressionParser.Builder<>(
+ this::getPrefix).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
+ .addBinaryOperator("^",
+ (o1, o2) -> o1.toExponent(o2.getMultiplier()), 1)
+ .build();
+
/**
* A parser that can parse unit dimension expressions.
*
@@ -1081,9 +1228,10 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final ExpressionParser<ObjectProduct<BaseDimension>> unitDimensionParser = new ExpressionParser.Builder<>(
- this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0).addSpaceFunction("*")
+ this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
+ .addSpaceFunction("*")
.addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build();
-
+
/**
* Creates the {@code UnitsDatabase}.
*
@@ -1096,43 +1244,42 @@ public final class UnitDatabase {
this.dimensions = new HashMap<>();
this.units = new PrefixedUnitMap(this.prefixlessUnits, this.prefixes);
}
-
+
/**
* Adds a unit dimension to the database.
*
- * @param name
- * dimension's name
- * @param dimension
- * dimension to add
- * @throws NullPointerException
- * if name or dimension is null
+ * @param name dimension's name
+ * @param dimension dimension to add
+ * @throws NullPointerException if name or dimension is null
* @since 2019-03-14
* @since v0.2.0
*/
- public void addDimension(final String name, final ObjectProduct<BaseDimension> dimension) {
- this.dimensions.put(Objects.requireNonNull(name, "name must not be null."),
+ public void addDimension(final String name,
+ final ObjectProduct<BaseDimension> dimension) {
+ this.dimensions.put(
+ Objects.requireNonNull(name, "name must not be null."),
Objects.requireNonNull(dimension, "dimension must not be null."));
}
-
+
/**
* Adds to the list from a line in a unit dimension file.
*
- * @param line
- * line to look at
- * @param lineCounter
- * number of line, for error messages
+ * @param line line to look at
+ * @param lineCounter number of line, for error messages
* @since 2019-04-10
* @since v0.2.0
*/
- private void addDimensionFromLine(final String line, final long lineCounter) {
+ private void addDimensionFromLine(final String line,
+ final long lineCounter) {
// ignore lines that start with a # sign - they're comments
if (line.isEmpty())
return;
if (line.contains("#")) {
- this.addDimensionFromLine(line.substring(0, line.indexOf("#")), lineCounter);
+ this.addDimensionFromLine(line.substring(0, line.indexOf("#")),
+ lineCounter);
return;
}
-
+
// divide line into name and expression
final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
@@ -1141,17 +1288,18 @@ public final class UnitDatabase {
lineCounter));
final String name = lineMatcher.group(1);
final String expression = lineMatcher.group(2);
-
+
if (name.endsWith(" ")) {
- System.err.printf("Warning - line %d's dimension name ends in a space", lineCounter);
+ System.err.printf("Warning - line %d's dimension name ends in a space",
+ lineCounter);
}
-
+
// if expression is "!", search for an existing dimension
// if no unit found, throw an error
if (expression.equals("!")) {
if (!this.containsDimensionName(name))
- throw new IllegalArgumentException(
- String.format("! used but no dimension found (line %d).", lineCounter));
+ throw new IllegalArgumentException(String.format(
+ "! used but no dimension found (line %d).", lineCounter));
} else {
// it's a unit, get the unit
final ObjectProduct<BaseDimension> dimension;
@@ -1161,20 +1309,17 @@ public final class UnitDatabase {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
-
+
this.addDimension(name, dimension);
}
}
-
+
/**
* Adds a unit prefix to the database.
*
- * @param name
- * prefix's name
- * @param prefix
- * prefix to add
- * @throws NullPointerException
- * if name or prefix is null
+ * @param name prefix's name
+ * @param prefix prefix to add
+ * @throws NullPointerException if name or prefix is null
* @since 2019-01-14
* @since v0.1.0
*/
@@ -1182,43 +1327,41 @@ public final class UnitDatabase {
this.prefixes.put(Objects.requireNonNull(name, "name must not be null."),
Objects.requireNonNull(prefix, "prefix must not be null."));
}
-
+
/**
* Adds a unit to the database.
*
- * @param name
- * unit's name
- * @param unit
- * unit to add
- * @throws NullPointerException
- * if unit is null
+ * @param name unit's name
+ * @param unit unit to add
+ * @throws NullPointerException if unit is null
* @since 2019-01-10
* @since v0.1.0
*/
public void addUnit(final String name, final Unit unit) {
- this.prefixlessUnits.put(Objects.requireNonNull(name, "name must not be null."),
+ this.prefixlessUnits.put(
+ Objects.requireNonNull(name, "name must not be null."),
Objects.requireNonNull(unit, "unit must not be null."));
}
-
+
/**
* Adds to the list from a line in a unit file.
*
- * @param line
- * line to look at
- * @param lineCounter
- * number of line, for error messages
+ * @param line line to look at
+ * @param lineCounter number of line, for error messages
* @since 2019-04-10
* @since v0.2.0
*/
- private void addUnitOrPrefixFromLine(final String line, final long lineCounter) {
+ private void addUnitOrPrefixFromLine(final String line,
+ final long lineCounter) {
// ignore lines that start with a # sign - they're comments
if (line.isEmpty())
return;
if (line.contains("#")) {
- this.addUnitOrPrefixFromLine(line.substring(0, line.indexOf("#")), lineCounter);
+ this.addUnitOrPrefixFromLine(line.substring(0, line.indexOf("#")),
+ lineCounter);
return;
}
-
+
// divide line into name and expression
final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
@@ -1226,18 +1369,20 @@ public final class UnitDatabase {
"Error at line %d: Lines of a unit file must consist of a unit name, then spaces or tabs, then a unit expression.",
lineCounter));
final String name = lineMatcher.group(1);
-
+
final String expression = lineMatcher.group(2);
-
+
if (name.endsWith(" ")) {
- System.err.printf("Warning - line %d's unit name ends in a space", lineCounter);
+ System.err.printf("Warning - line %d's unit name ends in a space",
+ lineCounter);
}
-
+
// if expression is "!", search for an existing unit
// if no unit found, throw an error
if (expression.equals("!")) {
if (!this.containsUnitName(name))
- throw new IllegalArgumentException(String.format("! used but no unit found (line %d).", lineCounter));
+ throw new IllegalArgumentException(String
+ .format("! used but no unit found (line %d).", lineCounter));
} else {
if (name.endsWith("-")) {
final UnitPrefix prefix;
@@ -1257,17 +1402,16 @@ public final class UnitDatabase {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
-
+
this.addUnit(name, unit);
}
}
}
-
+
/**
* Tests if the database has a unit dimension with this name.
*
- * @param name
- * name to test
+ * @param name name to test
* @return if database contains name
* @since 2019-03-14
* @since v0.2.0
@@ -1275,12 +1419,11 @@ public final class UnitDatabase {
public boolean containsDimensionName(final String name) {
return this.dimensions.containsKey(name);
}
-
+
/**
* Tests if the database has a unit prefix with this name.
*
- * @param name
- * name to test
+ * @param name name to test
* @return if database contains name
* @since 2019-01-13
* @since v0.1.0
@@ -1288,12 +1431,12 @@ public final class UnitDatabase {
public boolean containsPrefixName(final String name) {
return this.prefixes.containsKey(name);
}
-
+
/**
- * Tests if the database has a unit with this name, taking prefixes into consideration
+ * Tests if the database has a unit with this name, taking prefixes into
+ * consideration
*
- * @param name
- * name to test
+ * @param name name to test
* @return if database contains name
* @since 2019-01-13
* @since v0.1.0
@@ -1301,7 +1444,7 @@ public final class UnitDatabase {
public boolean containsUnitName(final String name) {
return this.units.containsKey(name);
}
-
+
/**
* @return a map mapping dimension names to dimensions
* @since 2019-04-13
@@ -1310,7 +1453,50 @@ public final class UnitDatabase {
public Map<String, ObjectProduct<BaseDimension>> dimensionMap() {
return Collections.unmodifiableMap(this.dimensions);
}
-
+
+ /**
+ * Evaluates a unit expression, following the same rules as
+ * {@link #getUnitFromExpression}.
+ *
+ * @param expression expression to parse
+ * @return {@code LinearUnitValue} representing value of expression
+ * @since 2020-08-04
+ */
+ public LinearUnitValue evaluateUnitExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // attempt to get a unit as an alias first
+ if (this.containsUnitName(expression))
+ return this.getLinearUnitValue(expression);
+
+ // force operators to have spaces
+ String modifiedExpression = expression;
+ modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
+ modifiedExpression = modifiedExpression.replaceAll("-", " - ");
+
+ // format expression
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
+ }
+
+ // the previous operation breaks negative numbers, fix them!
+ // (i.e. -2 becomes - 2)
+ // FIXME the previous operaton also breaks stuff like "1e-5"
+ for (int i = 0; i < modifiedExpression.length(); i++) {
+ if (modifiedExpression.charAt(i) == '-'
+ && (i < 2 || Arrays.asList('+', '-', '*', '/', '^')
+ .contains(modifiedExpression.charAt(i - 2)))) {
+ // found a broken negative number
+ modifiedExpression = modifiedExpression.substring(0, i + 1)
+ + modifiedExpression.substring(i + 2);
+ }
+ }
+
+ return this.unitValueExpressionParser.parseExpression(modifiedExpression);
+ }
+
/**
* Gets a unit dimension from the database using its name.
*
@@ -1318,8 +1504,7 @@ public final class UnitDatabase {
* This method accepts exponents, like "L^3"
* </p>
*
- * @param name
- * dimension's name
+ * @param name dimension's name
* @return dimension
* @since 2019-03-14
* @since v0.2.0
@@ -1328,86 +1513,87 @@ public final class UnitDatabase {
Objects.requireNonNull(name, "name must not be null.");
if (name.contains("^")) {
final String[] baseAndExponent = name.split("\\^");
-
- final ObjectProduct<BaseDimension> base = this.getDimension(baseAndExponent[0]);
-
+
+ final ObjectProduct<BaseDimension> base = this
+ .getDimension(baseAndExponent[0]);
+
final int exponent;
try {
- exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
+ exponent = Integer
+ .parseInt(baseAndExponent[baseAndExponent.length - 1]);
} catch (final NumberFormatException e2) {
throw new IllegalArgumentException("Exponent must be an integer.");
}
-
+
return base.toExponent(exponent);
}
return this.dimensions.get(name);
}
-
+
/**
* Uses the database's data to parse an expression into a unit dimension
* <p>
* The expression is a series of any of the following:
* <ul>
- * <li>The name of a unit dimension, which multiplies or divides the result based on preceding operators</li>
- * <li>The operators '*' and '/', which multiply and divide (note that just putting two unit dimensions next to each
- * other is equivalent to multiplication)</li>
+ * <li>The name of a unit dimension, which multiplies or divides the result
+ * based on preceding operators</li>
+ * <li>The operators '*' and '/', which multiply and divide (note that just
+ * putting two unit dimensions next to each other is equivalent to
+ * multiplication)</li>
* <li>The operator '^' which exponentiates. Exponents must be integers.</li>
* </ul>
*
- * @param expression
- * expression to parse
- * @throws IllegalArgumentException
- * if the expression cannot be parsed
- * @throws NullPointerException
- * if expression is null
+ * @param expression expression to parse
+ * @throws IllegalArgumentException if the expression cannot be parsed
+ * @throws NullPointerException if expression is null
* @since 2019-04-13
* @since v0.2.0
*/
- public ObjectProduct<BaseDimension> getDimensionFromExpression(final String expression) {
+ public ObjectProduct<BaseDimension> getDimensionFromExpression(
+ final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a dimension as an alias first
if (this.containsDimensionName(expression))
return this.getDimension(expression);
-
+
// force operators to have spaces
String modifiedExpression = expression;
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
-
+
// format expression
- for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS.entrySet()) {
- modifiedExpression = replacement.getKey().matcher(modifiedExpression).replaceAll(replacement.getValue());
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
}
-
+ modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^");
+
return this.unitDimensionParser.parseExpression(modifiedExpression);
}
-
+
/**
- * Gets a unit. If it is linear, cast it to a LinearUnit and return it. Otherwise, throw an
- * {@code IllegalArgumentException}.
+ * Gets a unit. If it is linear, cast it to a LinearUnit and return it.
+ * Otherwise, throw an {@code IllegalArgumentException}.
*
- * @param name
- * unit's name
+ * @param name unit's name
* @return unit
* @since 2019-03-22
* @since v0.2.0
*/
private LinearUnit getLinearUnit(final String name) {
// see if I am using a function-unit like tempC(100)
+ Objects.requireNonNull(name, "name may not be null");
if (name.contains("(") && name.contains(")")) {
// break it into function name and value
final List<String> parts = Arrays.asList(name.split("\\("));
if (parts.size() != 2)
- throw new IllegalArgumentException("Format nonlinear units like: unit(value).");
-
+ throw new IllegalArgumentException(
+ "Format nonlinear units like: unit(value).");
+
// solve the function
final Unit unit = this.getUnit(parts.get(0));
- final double value = Double.parseDouble(parts.get(1).substring(0, parts.get(1).length() - 1));
+ final double value = Double.parseDouble(
+ parts.get(1).substring(0, parts.get(1).length() - 1));
return LinearUnit.fromUnitValue(unit, value);
} else {
// get a linear unit
@@ -1415,15 +1601,27 @@ public final class UnitDatabase {
if (unit instanceof LinearUnit)
return (LinearUnit) unit;
else
- throw new IllegalArgumentException(String.format("%s is not a linear unit.", name));
+ throw new IllegalArgumentException(
+ String.format("%s is not a linear unit.", name));
}
}
-
+
+ /**
+ * Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be
+ * converted to their base units.
+ *
+ * @param name name of unit
+ * @return {@code LinearUnitValue} instance
+ * @since 2020-08-04
+ */
+ private LinearUnitValue getLinearUnitValue(final String name) {
+ return LinearUnitValue.getExact(this.getLinearUnit(name), 1);
+ }
+
/**
* Gets a unit prefix from the database from its name
*
- * @param name
- * prefix's name
+ * @param name prefix's name
* @return prefix
* @since 2019-01-10
* @since v0.1.0
@@ -1435,53 +1633,45 @@ public final class UnitDatabase {
return this.prefixes.get(name);
}
}
-
+
/**
* Gets a unit prefix from a prefix expression
* <p>
- * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
- * another prefix
+ * Currently, prefix expressions are much simpler than unit expressions: They
+ * are either a number or the name of another prefix
* </p>
*
- * @param expression
- * expression to input
+ * @param expression expression to input
* @return prefix
- * @throws IllegalArgumentException
- * if expression cannot be parsed
- * @throws NullPointerException
- * if any argument is null
+ * @throws IllegalArgumentException if expression cannot be parsed
+ * @throws NullPointerException if any argument is null
* @since 2019-01-14
* @since v0.1.0
*/
public UnitPrefix getPrefixFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias first
if (this.containsUnitName(expression))
return this.getPrefix(expression);
-
+
// force operators to have spaces
String modifiedExpression = expression;
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
-
+
// format expression
- for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS.entrySet()) {
- modifiedExpression = replacement.getKey().matcher(modifiedExpression).replaceAll(replacement.getValue());
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
}
-
+
return this.prefixExpressionParser.parseExpression(modifiedExpression);
}
-
+
/**
* Gets a unit from the database from its name, looking for prefixes.
*
- * @param name
- * unit's name
+ * @param name unit's name
* @return unit
* @since 2019-01-10
* @since v0.1.0
@@ -1491,98 +1681,113 @@ public final class UnitDatabase {
final double value = Double.parseDouble(name);
return SI.ONE.times(value);
} catch (final NumberFormatException e) {
- return this.units.get(name);
+ final Unit unit = this.units.get(name);
+ if (unit.getPrimaryName().isEmpty())
+ return unit.withName(NameSymbol.ofName(name));
+ else if (!unit.getPrimaryName().get().equals(name)) {
+ final Set<String> otherNames = new HashSet<>(unit.getOtherNames());
+ otherNames.add(unit.getPrimaryName().get());
+ return unit.withName(NameSymbol.ofNullable(name,
+ unit.getSymbol().orElse(null), otherNames));
+ } else if (!unit.getOtherNames().contains(name)) {
+ final Set<String> otherNames = new HashSet<>(unit.getOtherNames());
+ otherNames.add(name);
+ return unit.withName(
+ NameSymbol.ofNullable(unit.getPrimaryName().orElse(null),
+ unit.getSymbol().orElse(null), otherNames));
+ } else
+ return unit;
}
-
+
}
-
+
/**
* Uses the database's unit data to parse an expression into a unit
* <p>
* The expression is a series of any of the following:
* <ul>
- * <li>The name of a unit, which multiplies or divides the result based on preceding operators</li>
- * <li>The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
- * other is equivalent to multiplication)</li>
+ * <li>The name of a unit, which multiplies or divides the result based on
+ * preceding operators</li>
+ * <li>The operators '*' and '/', which multiply and divide (note that just
+ * putting two units or values next to each other is equivalent to
+ * multiplication)</li>
* <li>The operator '^' which exponentiates. Exponents must be integers.</li>
* <li>A number which is multiplied or divided</li>
* </ul>
* This method only works with linear units.
*
- * @param expression
- * expression to parse
- * @throws IllegalArgumentException
- * if the expression cannot be parsed
- * @throws NullPointerException
- * if expression is null
+ * @param expression expression to parse
+ * @throws IllegalArgumentException if the expression cannot be parsed
+ * @throws NullPointerException if expression is null
* @since 2019-01-07
* @since v0.1.0
*/
public Unit getUnitFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias first
if (this.containsUnitName(expression))
return this.getUnit(expression);
-
+
// force operators to have spaces
String modifiedExpression = expression;
modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
modifiedExpression = modifiedExpression.replaceAll("-", " - ");
- modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
- modifiedExpression = modifiedExpression.replaceAll("/", " / ");
- modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
-
- // fix broken spaces
- modifiedExpression = modifiedExpression.replaceAll(" +", " ");
// format expression
- for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS.entrySet()) {
- modifiedExpression = replacement.getKey().matcher(modifiedExpression).replaceAll(replacement.getValue());
+ for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
+ .entrySet()) {
+ modifiedExpression = replacement.getKey().matcher(modifiedExpression)
+ .replaceAll(replacement.getValue());
}
-
+
// the previous operation breaks negative numbers, fix them!
// (i.e. -2 becomes - 2)
for (int i = 0; i < modifiedExpression.length(); i++) {
if (modifiedExpression.charAt(i) == '-'
- && (i < 2 || Arrays.asList('+', '-', '*', '/', '^').contains(modifiedExpression.charAt(i - 2)))) {
+ && (i < 2 || Arrays.asList('+', '-', '*', '/', '^')
+ .contains(modifiedExpression.charAt(i - 2)))) {
// found a broken negative number
- modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2);
+ modifiedExpression = modifiedExpression.substring(0, i + 1)
+ + modifiedExpression.substring(i + 2);
}
}
-
+
return this.unitExpressionParser.parseExpression(modifiedExpression);
}
-
+
/**
- * Adds all dimensions from a file, using data from the database to parse them.
+ * Adds all dimensions from a file, using data from the database to parse
+ * them.
* <p>
- * Each line in the file should consist of a name and an expression (parsed by getDimensionFromExpression) separated
- * by any number of tab characters.
+ * Each line in the file should consist of a name and an expression (parsed
+ * by getDimensionFromExpression) separated by any number of tab characters.
* <p>
* <p>
* Allowed exceptions:
* <ul>
- * <li>Anything after a '#' character is considered a comment and ignored.</li>
+ * <li>Anything after a '#' character is considered a comment and
+ * ignored.</li>
* <li>Blank lines are also ignored</li>
- * <li>If an expression consists of a single exclamation point, instead of parsing it, this method will search the
- * database for an existing unit. If no unit is found, an IllegalArgumentException is thrown. This is used to define
- * initial units and ensure that the database contains them.</li>
+ * <li>If an expression consists of a single exclamation point, instead of
+ * parsing it, this method will search the database for an existing unit. If
+ * no unit is found, an IllegalArgumentException is thrown. This is used to
+ * define initial units and ensure that the database contains them.</li>
* </ul>
*
- * @param file
- * file to read
- * @throws IllegalArgumentException
- * if the file cannot be parsed, found or read
- * @throws NullPointerException
- * if file is null
+ * @param file file to read
+ * @throws IllegalArgumentException if the file cannot be parsed, found or
+ * read
+ * @throws NullPointerException if file is null
* @since 2019-01-13
* @since v0.1.0
*/
public void loadDimensionFile(final File file) {
Objects.requireNonNull(file, "file must not be null.");
- try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
- // while the reader has lines to read, read a line, then parse it, then add it
+ try (FileReader fileReader = new FileReader(file);
+ BufferedReader reader = new BufferedReader(fileReader)) {
+ // while the reader has lines to read, read a line, then parse it, then
+ // add it
long lineCounter = 0;
while (reader.ready()) {
this.addDimensionFromLine(reader.readLine(), ++lineCounter);
@@ -1593,36 +1798,38 @@ public final class UnitDatabase {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
-
+
/**
* Adds all units from a file, using data from the database to parse them.
* <p>
- * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
- * any number of tab characters.
+ * Each line in the file should consist of a name and an expression (parsed
+ * by getUnitFromExpression) separated by any number of tab characters.
* <p>
* <p>
* Allowed exceptions:
* <ul>
- * <li>Anything after a '#' character is considered a comment and ignored.</li>
+ * <li>Anything after a '#' character is considered a comment and
+ * ignored.</li>
* <li>Blank lines are also ignored</li>
- * <li>If an expression consists of a single exclamation point, instead of parsing it, this method will search the
- * database for an existing unit. If no unit is found, an IllegalArgumentException is thrown. This is used to define
- * initial units and ensure that the database contains them.</li>
+ * <li>If an expression consists of a single exclamation point, instead of
+ * parsing it, this method will search the database for an existing unit. If
+ * no unit is found, an IllegalArgumentException is thrown. This is used to
+ * define initial units and ensure that the database contains them.</li>
* </ul>
*
- * @param file
- * file to read
- * @throws IllegalArgumentException
- * if the file cannot be parsed, found or read
- * @throws NullPointerException
- * if file is null
+ * @param file file to read
+ * @throws IllegalArgumentException if the file cannot be parsed, found or
+ * read
+ * @throws NullPointerException if file is null
* @since 2019-01-13
* @since v0.1.0
*/
public void loadUnitsFile(final File file) {
Objects.requireNonNull(file, "file must not be null.");
- try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
- // while the reader has lines to read, read a line, then parse it, then add it
+ try (FileReader fileReader = new FileReader(file);
+ BufferedReader reader = new BufferedReader(fileReader)) {
+ // while the reader has lines to read, read a line, then parse it, then
+ // add it
long lineCounter = 0;
while (reader.ready()) {
this.addUnitOrPrefixFromLine(reader.readLine(), ++lineCounter);
@@ -1633,7 +1840,7 @@ public final class UnitDatabase {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
-
+
/**
* @return a map mapping prefix names to prefixes
* @since 2019-04-13
@@ -1642,33 +1849,40 @@ public final class UnitDatabase {
public Map<String, UnitPrefix> prefixMap() {
return Collections.unmodifiableMap(this.prefixes);
}
-
+
/**
- * @return a string stating the number of units, prefixes and dimensions in the database
+ * @return a string stating the number of units, prefixes and dimensions in
+ * the database
*/
@Override
public String toString() {
- return String.format("Unit Database with %d units, %d unit prefixes and %d dimensions",
- this.prefixlessUnits.size(), this.prefixes.size(), this.dimensions.size());
+ return String.format(
+ "Unit Database with %d units, %d unit prefixes and %d dimensions",
+ this.prefixlessUnits.size(), this.prefixes.size(),
+ this.dimensions.size());
}
-
+
/**
* Returns a map mapping unit names to units, including units with prefixes.
* <p>
- * The returned map is infinite in size if there is at least one unit and at least one prefix. If it is infinite,
- * some operations that only work with finite collections, like converting name/entry sets to arrays, will throw an
- * {@code IllegalStateException}.
+ * The returned map is infinite in size if there is at least one unit and at
+ * least one prefix. If it is infinite, some operations that only work with
+ * finite collections, like converting name/entry sets to arrays, will throw
+ * an {@code IllegalStateException}.
* </p>
* <p>
- * Specifically, the operations that will throw an IllegalStateException if the map is infinite in size are:
+ * Specifically, the operations that will throw an IllegalStateException if
+ * the map is infinite in size are:
* <ul>
* <li>{@code unitMap.entrySet().toArray()} (either overloading)</li>
* <li>{@code unitMap.keySet().toArray()} (either overloading)</li>
* </ul>
* </p>
* <p>
- * Because of ambiguities between prefixes (i.e. kilokilo = mega), the map's {@link PrefixedUnitMap#containsValue
- * containsValue} and {@link PrefixedUnitMap#values() values()} methods currently ignore prefixes.
+ * Because of ambiguities between prefixes (i.e. kilokilo = mega), the map's
+ * {@link PrefixedUnitMap#containsValue containsValue} and
+ * {@link PrefixedUnitMap#values() values()} methods currently ignore
+ * prefixes.
* </p>
*
* @return a map mapping unit names to units, including prefixed names
@@ -1676,9 +1890,10 @@ public final class UnitDatabase {
* @since v0.2.0
*/
public Map<String, Unit> unitMap() {
- return this.units; // PrefixedUnitMap is immutable so I don't need to make an unmodifiable map.
+ return this.units; // PrefixedUnitMap is immutable so I don't need to make
+ // an unmodifiable map.
}
-
+
/**
* @return a map mapping unit names to units, ignoring prefixes
* @since 2019-04-13