summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2019-04-13 10:05:08 -0400
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2019-04-13 10:05:08 -0400
commit8e613844ae19a4dea2089ac34c1f0ae650eaeae7 (patch)
tree43f3fcf2fc7fafb840156a6fe0a5596ba091a904
parentec036fdad931fbbd7dec28b864150f8668e91b41 (diff)
The dimension selector now loads dimensions from a file.
The dimension selector does nothing, as its purpose is to filter a list which does not exist yet, but it does correctly load the options.
-rw-r--r--CHANGELOG.org4
-rw-r--r--dimensionfile.txt13
-rwxr-xr-xsrc/org/unitConverter/UnitsDatabase.java265
-rwxr-xr-xsrc/org/unitConverter/converterGUI/UnitConverterGUI.java30
-rwxr-xr-xsrc/org/unitConverter/unit/BaseUnit.java24
-rw-r--r--src/org/unitConverter/unit/LinearUnit.java10
6 files changed, 275 insertions, 71 deletions
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 8a79c46..46197dc 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -6,12 +6,14 @@ All notable changes in this project will be shown in this file.
- Moved project to Maven
- Downgraded JUnit to 4.11
- BaseUnit is now a subclass of LinearUnit
- - Comments can now start in the middle of lines
+ - In unit files, Comments can now start in the middle of lines
+ - UnitsDatabase.addAllFromFile() has been renamed to loadUnitsFile()
*** Added
- GUI for a selection-based unit converter
- The UnitDatabase now stores dimensions.
- A system to parse mathematical expressions, used to parse unit expressions.
- You can now add and subtract in unit expressions!
+ - Instructions for obtaining unit instances are provided in the relevant classes
** v0.1.0
NOTE: At this stage, the API is subject to significant change.
*** Added
diff --git a/dimensionfile.txt b/dimensionfile.txt
new file mode 100644
index 0000000..d3c068c
--- /dev/null
+++ b/dimensionfile.txt
@@ -0,0 +1,13 @@
+# A file for the unit dimensions in my unit converter program
+
+# SI Base Dimensions
+# ! means "look for an existing dimension which I will load at the start"
+# This is necessary because every dimension must be defined by others, and I need somewhere to start.
+
+LENGTH !
+MASS !
+TIME !
+ELECTRIC_CURRENT !
+TEMPERATURE !
+QUANTITY !
+LUMINOUS_INTENSITY ! \ No newline at end of file
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 69b25d8..626f145 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -126,46 +126,6 @@ public final class UnitsDatabase {
}
/**
- * 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.
- * <p>
- * <p>
- * Allowed exceptions:
- * <ul>
- * <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>
- * </ul>
- *
- * @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 addAllFromFile(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
- long lineCounter = 0;
- while (reader.ready()) {
- this.addFromLine(reader.readLine(), ++lineCounter);
- }
- } catch (final FileNotFoundException e) {
- throw new IllegalArgumentException("Could not find file " + file, e);
- } catch (final IOException e) {
- throw new IllegalArgumentException("Could not read file " + file, e);
- }
- }
-
- /**
* Adds a unit dimension to the database.
*
* @param name
@@ -182,7 +142,7 @@ public final class UnitsDatabase {
}
/**
- * Adds to the list from a line in a unit file.
+ * Adds to the list from a line in a unit dimension file.
*
* @param line
* line to look at
@@ -190,12 +150,12 @@ public final class UnitsDatabase {
* number of line, for error messages
* @since 2019-04-10
*/
- private void addFromLine(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.addFromLine(line.substring(0, line.indexOf("#")), lineCounter);
+ this.addDimensionFromLine(line.substring(0, line.indexOf("#")), lineCounter);
return;
}
@@ -203,42 +163,32 @@ public final class UnitsDatabase {
final String[] parts = line.split("\t");
if (parts.length < 2)
throw new IllegalArgumentException(String.format(
- "Lines must consist of a unit name and its definition, separated by tab(s) (line %d).",
+ "Lines must consist of a dimension name and its definition, separated by tab(s) (line %d).",
lineCounter));
final String name = parts[0];
final String expression = parts[parts.length - 1];
if (name.endsWith(" ")) {
- System.err.printf("Warning - line %d's unit 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 unit
+ // if expression is "!", search for an existing dimension
// 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));
+ if (!this.containsDimensionName(name))
+ throw new IllegalArgumentException(
+ String.format("! used but no dimension found (line %d).", lineCounter));
} else {
- if (name.endsWith("-")) {
- final UnitPrefix prefix;
- try {
- prefix = this.getPrefixFromExpression(expression);
- } catch (final IllegalArgumentException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
- this.addPrefix(name.substring(0, name.length() - 1), prefix);
- } else {
- // it's a unit, get the unit
- final Unit unit;
- try {
- unit = this.getUnitFromExpression(expression);
- } catch (final IllegalArgumentException e) {
- System.err.printf("Parsing error on line %d:%n", lineCounter);
- throw e;
- }
-
- this.addUnit(name, unit);
+ // it's a unit, get the unit
+ final UnitDimension dimension;
+ try {
+ dimension = this.getDimensionFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
}
+
+ this.addDimension(name, dimension);
}
}
@@ -277,6 +227,67 @@ public final class UnitsDatabase {
}
/**
+ * Adds to the list from a line in a unit file.
+ *
+ * @param line
+ * line to look at
+ * @param lineCounter
+ * number of line, for error messages
+ * @since 2019-04-10
+ */
+ 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);
+ return;
+ }
+
+ // divide line into name and expression
+ final String[] parts = line.split("\t");
+ if (parts.length < 2)
+ throw new IllegalArgumentException(String.format(
+ "Lines must consist of a unit name and its definition, separated by tab(s) (line %d).",
+ lineCounter));
+ final String name = parts[0];
+ final String expression = parts[parts.length - 1];
+
+ if (name.endsWith(" ")) {
+ System.err.printf("Warning - line %d's unit name ends in a space", lineCounter);
+ }
+
+ // if expression is "!", search for an existing unit
+ // if no unit found, throw an error
+ if (expression.equals("!")) {
+ if (!this.containsUnitName(name))
+ throw new IllegalArgumentException(String.format("! used but no unit found (line %d).", lineCounter));
+ } else {
+ if (name.endsWith("-")) {
+ final UnitPrefix prefix;
+ try {
+ prefix = this.getPrefixFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
+ }
+ this.addPrefix(name.substring(0, name.length() - 1), prefix);
+ } else {
+ // it's a unit, get the unit
+ final Unit unit;
+ try {
+ unit = this.getUnitFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ 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
@@ -373,6 +384,44 @@ public final class UnitsDatabase {
}
/**
+ * 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 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
+ * @since 2019-04-13
+ */
+ public UnitDimension 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(" +", " ");
+
+ 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}.
*
@@ -599,6 +648,86 @@ public final class UnitsDatabase {
}
/**
+ * 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.
+ * <p>
+ * <p>
+ * Allowed exceptions:
+ * <ul>
+ * <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>
+ * </ul>
+ *
+ * @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
+ long lineCounter = 0;
+ while (reader.ready()) {
+ this.addDimensionFromLine(reader.readLine(), ++lineCounter);
+ }
+ } catch (final FileNotFoundException e) {
+ throw new IllegalArgumentException("Could not find file " + file, e);
+ } catch (final IOException e) {
+ throw new IllegalArgumentException("Could not read file " + file, e);
+ }
+ }
+
+ /**
+ * 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.
+ * <p>
+ * <p>
+ * Allowed exceptions:
+ * <ul>
+ * <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>
+ * </ul>
+ *
+ * @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
+ long lineCounter = 0;
+ while (reader.ready()) {
+ this.addUnitOrPrefixFromLine(reader.readLine(), ++lineCounter);
+ }
+ } catch (final FileNotFoundException e) {
+ throw new IllegalArgumentException("Could not find file " + file, e);
+ } catch (final IOException e) {
+ throw new IllegalArgumentException("Could not read file " + file, e);
+ }
+ }
+
+ /**
* @return an immutable set of all of the unit names in this database, ignoring prefixes
* @since 2019-01-14
* @since v0.1.0
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index fd40ff4..4f5ebeb 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -78,6 +78,9 @@ final class UnitConverterGUI {
/** The names of all of the prefixes */
private final DelegateListModel<String> prefixNamesFiltered;
+ /** The names of all of the dimensions */
+ private final List<String> dimensionNames;
+
private final Comparator<String> prefixNameComparator;
private int significantFigures = 6;
@@ -109,7 +112,17 @@ final class UnitConverterGUI {
this.units.addUnit("tempCelsius", NonlinearUnits.CELSIUS);
this.units.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
- this.units.addAllFromFile(new File("unitsfile.txt"));
+ // load initial dimensions
+ this.units.addDimension("LENGTH", StandardDimensions.LENGTH);
+ this.units.addDimension("MASS", StandardDimensions.MASS);
+ this.units.addDimension("TIME", StandardDimensions.TIME);
+ this.units.addDimension("ELECTRIC_CURRENT", StandardDimensions.ELECTRIC_CURRENT);
+ this.units.addDimension("TEMPERATURE", StandardDimensions.TEMPERATURE);
+ this.units.addDimension("QUANTITY", StandardDimensions.QUANTITY);
+ this.units.addDimension("LUMINOUS_INTENSITY", StandardDimensions.LUMINOUS_INTENSITY);
+
+ this.units.loadUnitsFile(new File("unitsfile.txt"));
+ this.units.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.
@@ -143,6 +156,9 @@ final class UnitConverterGUI {
this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet()));
this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator
+ this.dimensionNames = new DelegateListModel<>(new ArrayList<>(this.units.dimensionNameSet()));
+ 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 BaseUnit && ((BaseUnit) unit).isFullBase();
@@ -223,6 +239,14 @@ final class UnitConverterGUI {
}
/**
+ * @return a list of all of the unit dimensions
+ * @since 2019-04-13
+ */
+ public final List<String> dimensionNameList() {
+ return this.dimensionNames;
+ }
+
+ /**
* Filters the filtered model for units
*
* @param filter
@@ -531,8 +555,10 @@ final class UnitConverterGUI {
inBetweenPanel.setLayout(new BorderLayout());
{ // dimension selector
+ final List<String> dimensionNameList = this.presenter.dimensionNameList();
+ dimensionNameList.add(0, "Select a dimension...");
final JComboBox<String> dimensionSelector = new JComboBox<>(
- new String[] {"Select dimension..."});
+ dimensionNameList.toArray(new String[0]));
inBetweenPanel.add(dimensionSelector, BorderLayout.PAGE_START);
}
diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
index 643272f..8bac866 100755
--- a/src/org/unitConverter/unit/BaseUnit.java
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -18,11 +18,35 @@ package org.unitConverter.unit;
import java.util.Objects;
+import org.unitConverter.dimension.StandardDimensions;
import org.unitConverter.dimension.UnitDimension;
/**
* A unit that is the base for its dimension. It does not have to be for a base dimension, so units like the Newton and
* Joule are still base units.
+ * <p>
+ * {@code BaseUnit} does not have any public constructors or static factories. There are two ways to obtain
+ * {@code BaseUnit} instances.
+ * <ol>
+ * <li>The class {@link SI} in this package has constants for all of the SI base units. You can use these constants and
+ * multiply or divide them to get other units. For example:
+ *
+ * <pre>
+ * BaseUnit JOULE = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
+ * </pre>
+ *
+ * </li>
+ * <li>You can also query a unit system for a base unit using a unit dimension. The previously mentioned {@link SI}
+ * class can do this for SI and SI-derived units (including imperial and USC), but if you want to use another system,
+ * this is the way to do it. {@link StandardDimensions} contains common unit dimensions that you can use for this. Here
+ * is an example:
+ *
+ * <pre>
+ * BaseUnit JOULE = SI.SI.getBaseUnit(StandardDimensions.ENERGY);
+ * </pre>
+ *
+ * </li>
+ * </ol>
*
* @author Adrien Hopkins
* @since 2018-12-23
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
index c755f79..5b2680b 100644
--- a/src/org/unitConverter/unit/LinearUnit.java
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -23,6 +23,16 @@ import org.unitConverter.math.DecimalComparison;
/**
* A unit that is equal to a certain number multiplied by its base.
+ * <p>
+ * {@code LinearUnit} does not have any public constructors or static factories. In order to obtain a {@code LinearUnit}
+ * instance, multiply its base by the conversion factor. Example:
+ *
+ * <pre>
+ * LinearUnit foot = METRE.times(0.3048);
+ * </pre>
+ *
+ * (where {@code METRE} is a {@code BaseUnit} instance)
+ * </p>
*
* @author Adrien Hopkins
* @since 2018-12-22