diff options
author | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2019-04-13 10:05:08 -0400 |
---|---|---|
committer | Adrien Hopkins <adrien.p.hopkins@gmail.com> | 2019-04-13 10:05:08 -0400 |
commit | 8e613844ae19a4dea2089ac34c1f0ae650eaeae7 (patch) | |
tree | 43f3fcf2fc7fafb840156a6fe0a5596ba091a904 | |
parent | ec036fdad931fbbd7dec28b864150f8668e91b41 (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.org | 4 | ||||
-rw-r--r-- | dimensionfile.txt | 13 | ||||
-rwxr-xr-x | src/org/unitConverter/UnitsDatabase.java | 265 | ||||
-rwxr-xr-x | src/org/unitConverter/converterGUI/UnitConverterGUI.java | 30 | ||||
-rwxr-xr-x | src/org/unitConverter/unit/BaseUnit.java | 24 | ||||
-rw-r--r-- | src/org/unitConverter/unit/LinearUnit.java | 10 |
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 |