From f21b9678ddc7bb6b59f0f076bb978a6993890548 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 20 Feb 2019 20:57:06 -0500
Subject: Created a GUI for a planned dimension-based converter.
The GUI does not do anything right now, but the idea is that the user
will select a dimension (length, mass, energy) and then select units
to convert from a searchable list.
---
.../converterGUI/UnitConverterGUI.java | 124 +++++++++++++++++++--
1 file changed, 115 insertions(+), 9 deletions(-)
diff --git a/src/unitConverter/converterGUI/UnitConverterGUI.java b/src/unitConverter/converterGUI/UnitConverterGUI.java
index b54e0da..cf78ea8 100755
--- a/src/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/unitConverter/converterGUI/UnitConverterGUI.java
@@ -21,6 +21,7 @@ import java.awt.GridLayout;
import java.io.File;
import java.math.BigDecimal;
import java.math.MathContext;
+import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -28,7 +29,10 @@ import java.util.function.Predicate;
import javax.swing.BorderFactory;
import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
+import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
@@ -476,15 +480,117 @@ final class UnitConverterGUI {
final JTabbedPane masterPane = new JTabbedPane();
masterPanel.add(masterPane, BorderLayout.CENTER);
- { // panel for unit conversion
- final JPanel convertPanel = new JPanel();
- masterPane.addTab("Convert Units", convertPanel);
+ { // a panel for unit conversion using a selector
+ final JPanel convertUnitPanel = new JPanel();
+ masterPane.addTab("Convert Units", convertUnitPanel);
- convertPanel.setLayout(new GridLayout(5, 1));
+ convertUnitPanel.setLayout(new BorderLayout());
+
+ { // panel for input part
+ final JPanel inputPanel = new JPanel();
+ convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
+
+ inputPanel.setLayout(new GridLayout(1, 3));
+
+ { // panel for From things
+ final JPanel fromPanel = new JPanel();
+ inputPanel.add(fromPanel);
+
+ fromPanel.setLayout(new BorderLayout());
+
+ { // search box for from
+ final JTextField fromSearch = new JTextField("Search...");
+ fromPanel.add(fromSearch, BorderLayout.PAGE_START);
+ }
+
+ { // list for From units
+ final JList fromList = new JList<>();
+ fromPanel.add(fromList, BorderLayout.CENTER);
+ }
+ }
+
+ { // for dimension selector and arrow that represents conversion
+ final JPanel inBetweenPanel = new JPanel();
+ inputPanel.add(inBetweenPanel);
+
+ inBetweenPanel.setLayout(new BorderLayout());
+
+ { // dimension selector
+ final JComboBox dimensionSelector = new JComboBox<>(
+ new String[] {"Select dimension..."});
+ 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
+ final JPanel toPanel = new JPanel();
+ inputPanel.add(toPanel);
+
+ toPanel.setLayout(new BorderLayout());
+
+ { // search box for to
+ final JTextField toSearch = new JTextField("Search...");
+ toPanel.add(toSearch, BorderLayout.PAGE_START);
+ }
+
+ { // list for To units
+ final JList toList = new JList<>();
+ toPanel.add(toList, BorderLayout.CENTER);
+ }
+ }
+
+ }
+
+ { // 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);
+ }
+
+ { // value to convert
+ final JTextField valueInput = new JFormattedTextField(
+ new DecimalFormat("###############0.################"));
+ valueInputPanel.add(valueInput, BorderLayout.CENTER);
+ }
+ }
+
+ { // button to convert
+ final JButton convertButton = new JButton("Convert");
+ outputPanel.add(convertButton);
+ }
+
+ { // output of conversion
+ final JLabel outputLabel = new JLabel();
+ outputPanel.add(outputLabel);
+ }
+ }
+ }
+
+ { // panel for unit conversion using expressions
+ final JPanel convertExpressionPanel = new JPanel();
+ masterPane.addTab("Convert Unit Expressions", convertExpressionPanel);
+
+ convertExpressionPanel.setLayout(new GridLayout(5, 1));
{ // panel for units to convert from
final JPanel fromPanel = new JPanel();
- convertPanel.add(fromPanel);
+ convertExpressionPanel.add(fromPanel);
fromPanel.setBorder(BorderFactory.createTitledBorder("From"));
fromPanel.setLayout(new GridLayout(1, 1));
@@ -496,7 +602,7 @@ final class UnitConverterGUI {
{ // panel for units to convert to
final JPanel toPanel = new JPanel();
- convertPanel.add(toPanel);
+ convertExpressionPanel.add(toPanel);
toPanel.setBorder(BorderFactory.createTitledBorder("To"));
toPanel.setLayout(new GridLayout(1, 1));
@@ -508,14 +614,14 @@ final class UnitConverterGUI {
{ // button to convert
final JButton convertButton = new JButton("Convert!");
- convertPanel.add(convertButton);
+ convertExpressionPanel.add(convertButton);
convertButton.addActionListener(e -> this.presenter.convert());
}
{ // output of conversion
final JPanel outputPanel = new JPanel();
- convertPanel.add(outputPanel);
+ convertExpressionPanel.add(outputPanel);
outputPanel.setBorder(BorderFactory.createTitledBorder("Output"));
outputPanel.setLayout(new GridLayout(1, 1));
@@ -528,7 +634,7 @@ final class UnitConverterGUI {
{ // panel for specifying precision
final JPanel sigDigPanel = new JPanel();
- convertPanel.add(sigDigPanel);
+ convertExpressionPanel.add(sigDigPanel);
sigDigPanel.setBorder(BorderFactory.createTitledBorder("Significant Digits"));
--
cgit v1.2.3
From 5c4cd6d206e195d0c5efce747e8670f8e77cb59c Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Thu, 14 Mar 2019 18:07:12 -0400
Subject: Added unit dimensions to the unit database.
---
CHANGELOG.org | 4 +++
src/unitConverter/UnitsDatabase.java | 69 ++++++++++++++++++++++++++++++++----
2 files changed, 67 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 1dbe268..280ceb3 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -1,6 +1,10 @@
* Changelog
All notable changes in this project will be shown in this file.
+** Unreleased
+*** Added
+ - GUI for a selection-based unit converter
+ - The UnitDatabase now stores dimensions.
** v0.1.0
NOTE: At this stage, the API is subject to significant change.
*** Added
diff --git a/src/unitConverter/UnitsDatabase.java b/src/unitConverter/UnitsDatabase.java
index 4816db1..9f5a6a2 100755
--- a/src/unitConverter/UnitsDatabase.java
+++ b/src/unitConverter/UnitsDatabase.java
@@ -37,7 +37,7 @@ import unitConverter.unit.Unit;
import unitConverter.unit.UnitPrefix;
/**
- * A database of units.
+ * A database of units and prefixes, and their names.
*
* @author Adrien Hopkins
* @since 2019-01-07
@@ -60,6 +60,13 @@ public final class UnitsDatabase {
*/
private final Map prefixes;
+ /**
+ * The dimensions in this system.
+ *
+ * @since 2019-03-14
+ */
+ private final Map dimensions;
+
/**
* Creates the {@code UnitsDatabase}.
*
@@ -69,6 +76,7 @@ public final class UnitsDatabase {
public UnitsDatabase() {
this.units = new HashMap<>();
this.prefixes = new HashMap<>();
+ this.dimensions = new HashMap<>();
}
/**
@@ -164,14 +172,30 @@ public final class UnitsDatabase {
}
/**
- * Adds a unit prefix to the database using a custom name
+ * 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
+ * @since 2019-03-14
+ */
+ public void addDimension(final String name, final UnitDimension dimension) {
+ this.dimensions.put(Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(dimension, "dimension must not be null."));
+ }
+
+ /**
+ * Adds a unit prefix to the database.
*
* @param name
* prefix's name
* @param prefix
* prefix to add
* @throws NullPointerException
- * if name or unit is null
+ * if name or prefix is null
* @since 2019-01-14
* @since v0.1.0
*/
@@ -181,7 +205,7 @@ public final class UnitsDatabase {
}
/**
- * Adds a unit to the database using a custom name
+ * Adds a unit to the database.
*
* @param name
* unit's name
@@ -193,7 +217,20 @@ public final class UnitsDatabase {
* @since v0.1.0
*/
public void addUnit(final String name, final Unit unit) {
- this.units.put(name, Objects.requireNonNull(unit, "unit must not be null."));
+ this.units.put(Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(unit, "unit must not be null."));
+ }
+
+ /**
+ * Tests if the database has a unit dimension with this name.
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-03-14
+ */
+ public boolean containsDimensionName(final String name) {
+ return this.dimensions.containsKey(name);
}
/**
@@ -210,7 +247,7 @@ public final class UnitsDatabase {
}
/**
- * Tests if the database has a unit prefix with this name
+ * Tests if the database has a unit prefix with this name.
*
* @param name
* name to test
@@ -241,6 +278,26 @@ public final class UnitsDatabase {
return this.units.containsKey(name);
}
+ /**
+ * @return an immutable set of all of the dimension names in this database.
+ * @since 2019-03-14
+ */
+ public Set dimensionNameSet() {
+ return Collections.unmodifiableSet(this.dimensions.keySet());
+ }
+
+ /**
+ * Gets a unit dimension from the database using its name.
+ *
+ * @param name
+ * dimension's name
+ * @return dimension
+ * @since 2019-03-14
+ */
+ public UnitDimension getDimension(final String name) {
+ return this.dimensions.get(name);
+ }
+
/**
* Gets a unit prefix from the database from its name
*
--
cgit v1.2.3
From b20cd4223b4ffc03e334627a82ca4eff9738912c Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 16 Mar 2019 14:55:07 -0400
Subject: Moved project to Maven.
---
.classpath | 22 +-
.gitlab-ci.yml | 14 +
.project | 6 +
.settings/org.eclipse.jdt.core.prefs | 2 +
.settings/org.eclipse.m2e.core.prefs | 4 +
pom.xml | 37 +
src/org/unitConverter/UnitsDatabase.java | 641 +++++++++++++++++
.../converterGUI/DelegateListModel.java | 232 ++++++
.../converterGUI/FilterComparator.java | 93 +++
.../unitConverter/converterGUI/GridBagBuilder.java | 479 +++++++++++++
.../converterGUI/UnitConverterGUI.java | 777 +++++++++++++++++++++
.../unitConverter/converterGUI/package-info.java | 23 +
src/org/unitConverter/dimension/BaseDimension.java | 40 ++
.../dimension/OtherBaseDimension.java | 55 ++
.../unitConverter/dimension/SIBaseDimension.java | 57 ++
.../dimension/StandardDimensions.java | 80 +++
src/org/unitConverter/dimension/UnitDimension.java | 241 +++++++
.../unitConverter/dimension/UnitDimensionTest.java | 77 ++
src/org/unitConverter/dimension/package-info.java | 23 +
src/org/unitConverter/package-info.java | 23 +
src/org/unitConverter/unit/AbstractUnit.java | 172 +++++
src/org/unitConverter/unit/BaseUnit.java | 180 +++++
src/org/unitConverter/unit/DefaultUnitPrefix.java | 67 ++
src/org/unitConverter/unit/LinearUnit.java | 184 +++++
src/org/unitConverter/unit/NonlinearUnits.java | 57 ++
src/org/unitConverter/unit/SI.java | 74 ++
src/org/unitConverter/unit/SIPrefix.java | 54 ++
src/org/unitConverter/unit/Unit.java | 110 +++
src/org/unitConverter/unit/UnitPrefix.java | 33 +
src/org/unitConverter/unit/UnitSystem.java | 53 ++
src/org/unitConverter/unit/UnitTest.java | 46 ++
src/org/unitConverter/unit/package-info.java | 23 +
src/unitConverter/UnitsDatabase.java | 641 -----------------
.../converterGUI/DelegateListModel.java | 232 ------
.../converterGUI/FilterComparator.java | 93 ---
src/unitConverter/converterGUI/GridBagBuilder.java | 479 -------------
.../converterGUI/UnitConverterGUI.java | 777 ---------------------
src/unitConverter/converterGUI/package-info.java | 23 -
src/unitConverter/dimension/BaseDimension.java | 40 --
.../dimension/OtherBaseDimension.java | 55 --
src/unitConverter/dimension/SIBaseDimension.java | 57 --
.../dimension/StandardDimensions.java | 80 ---
src/unitConverter/dimension/UnitDimension.java | 241 -------
src/unitConverter/dimension/UnitDimensionTest.java | 77 --
src/unitConverter/dimension/package-info.java | 23 -
src/unitConverter/package-info.java | 23 -
src/unitConverter/unit/AbstractUnit.java | 172 -----
src/unitConverter/unit/BaseUnit.java | 180 -----
src/unitConverter/unit/DefaultUnitPrefix.java | 67 --
src/unitConverter/unit/LinearUnit.java | 184 -----
src/unitConverter/unit/NonlinearUnits.java | 57 --
src/unitConverter/unit/SI.java | 74 --
src/unitConverter/unit/SIPrefix.java | 54 --
src/unitConverter/unit/Unit.java | 110 ---
src/unitConverter/unit/UnitPrefix.java | 33 -
src/unitConverter/unit/UnitSystem.java | 53 --
src/unitConverter/unit/UnitTest.java | 47 --
src/unitConverter/unit/package-info.java | 23 -
58 files changed, 3975 insertions(+), 3899 deletions(-)
create mode 100644 .gitlab-ci.yml
create mode 100644 .settings/org.eclipse.m2e.core.prefs
create mode 100644 pom.xml
create mode 100755 src/org/unitConverter/UnitsDatabase.java
create mode 100755 src/org/unitConverter/converterGUI/DelegateListModel.java
create mode 100755 src/org/unitConverter/converterGUI/FilterComparator.java
create mode 100755 src/org/unitConverter/converterGUI/GridBagBuilder.java
create mode 100755 src/org/unitConverter/converterGUI/UnitConverterGUI.java
create mode 100644 src/org/unitConverter/converterGUI/package-info.java
create mode 100755 src/org/unitConverter/dimension/BaseDimension.java
create mode 100755 src/org/unitConverter/dimension/OtherBaseDimension.java
create mode 100755 src/org/unitConverter/dimension/SIBaseDimension.java
create mode 100755 src/org/unitConverter/dimension/StandardDimensions.java
create mode 100755 src/org/unitConverter/dimension/UnitDimension.java
create mode 100755 src/org/unitConverter/dimension/UnitDimensionTest.java
create mode 100755 src/org/unitConverter/dimension/package-info.java
create mode 100644 src/org/unitConverter/package-info.java
create mode 100644 src/org/unitConverter/unit/AbstractUnit.java
create mode 100755 src/org/unitConverter/unit/BaseUnit.java
create mode 100755 src/org/unitConverter/unit/DefaultUnitPrefix.java
create mode 100644 src/org/unitConverter/unit/LinearUnit.java
create mode 100755 src/org/unitConverter/unit/NonlinearUnits.java
create mode 100644 src/org/unitConverter/unit/SI.java
create mode 100755 src/org/unitConverter/unit/SIPrefix.java
create mode 100755 src/org/unitConverter/unit/Unit.java
create mode 100755 src/org/unitConverter/unit/UnitPrefix.java
create mode 100755 src/org/unitConverter/unit/UnitSystem.java
create mode 100755 src/org/unitConverter/unit/UnitTest.java
create mode 100644 src/org/unitConverter/unit/package-info.java
delete mode 100755 src/unitConverter/UnitsDatabase.java
delete mode 100755 src/unitConverter/converterGUI/DelegateListModel.java
delete mode 100755 src/unitConverter/converterGUI/FilterComparator.java
delete mode 100755 src/unitConverter/converterGUI/GridBagBuilder.java
delete mode 100755 src/unitConverter/converterGUI/UnitConverterGUI.java
delete mode 100644 src/unitConverter/converterGUI/package-info.java
delete mode 100755 src/unitConverter/dimension/BaseDimension.java
delete mode 100755 src/unitConverter/dimension/OtherBaseDimension.java
delete mode 100755 src/unitConverter/dimension/SIBaseDimension.java
delete mode 100755 src/unitConverter/dimension/StandardDimensions.java
delete mode 100755 src/unitConverter/dimension/UnitDimension.java
delete mode 100755 src/unitConverter/dimension/UnitDimensionTest.java
delete mode 100755 src/unitConverter/dimension/package-info.java
delete mode 100644 src/unitConverter/package-info.java
delete mode 100644 src/unitConverter/unit/AbstractUnit.java
delete mode 100755 src/unitConverter/unit/BaseUnit.java
delete mode 100755 src/unitConverter/unit/DefaultUnitPrefix.java
delete mode 100644 src/unitConverter/unit/LinearUnit.java
delete mode 100755 src/unitConverter/unit/NonlinearUnits.java
delete mode 100644 src/unitConverter/unit/SI.java
delete mode 100755 src/unitConverter/unit/SIPrefix.java
delete mode 100755 src/unitConverter/unit/Unit.java
delete mode 100755 src/unitConverter/unit/UnitPrefix.java
delete mode 100755 src/unitConverter/unit/UnitSystem.java
delete mode 100755 src/unitConverter/unit/UnitTest.java
delete mode 100644 src/unitConverter/unit/package-info.java
diff --git a/.classpath b/.classpath
index 145a671..72d7394 100644
--- a/.classpath
+++ b/.classpath
@@ -1,7 +1,21 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..7c2efb0
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,14 @@
+# attempt at ci
+
+image: maven:3-jdk-8
+
+
+build:
+ stage: build
+ script: "mvn clean -B"
+ script: "mvn compile -B"
+
+
+test:
+ stage: test
+ script: "mvn verify"
diff --git a/.project b/.project
index 656186b..56f686e 100644
--- a/.project
+++ b/.project
@@ -10,8 +10,14 @@
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+ org.eclipse.m2e.core.maven2Natureorg.eclipse.jdt.core.javanature
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 3a21537..8445b6b 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -8,4 +8,6 @@ org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8
diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3aede3b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,37 @@
+
+ 4.0.0
+ org.unitConverter
+ unitConverter
+ 0.1.0
+ Unit Converter
+ A Java unit converter inspired by GNU Units
+
+ src
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+ 1.8
+ 1.8
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+ org.unitConverter.converterGUI.UnitConverterGUI
+
+
+
+
+
+
+ junit
+ junit
+ 4.11
+
+
+
\ No newline at end of file
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
new file mode 100755
index 0000000..4d41735
--- /dev/null
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -0,0 +1,641 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.unitConverter.dimension.UnitDimension;
+import org.unitConverter.unit.AbstractUnit;
+import org.unitConverter.unit.BaseUnit;
+import org.unitConverter.unit.DefaultUnitPrefix;
+import org.unitConverter.unit.LinearUnit;
+import org.unitConverter.unit.SI;
+import org.unitConverter.unit.Unit;
+import org.unitConverter.unit.UnitPrefix;
+
+/**
+ * A database of units and prefixes, and their names.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+public final class UnitsDatabase {
+ /**
+ * The units in this system.
+ *
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+ private final Map units;
+
+ /**
+ * The unit prefixes in this system.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private final Map prefixes;
+
+ /**
+ * The dimensions in this system.
+ *
+ * @since 2019-03-14
+ */
+ private final Map dimensions;
+
+ /**
+ * Creates the {@code UnitsDatabase}.
+ *
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public UnitsDatabase() {
+ this.units = new HashMap<>();
+ this.prefixes = new HashMap<>();
+ this.dimensions = new HashMap<>();
+ }
+
+ /**
+ * Adds all units from a file, using data from the database to parse them.
+ *
+ * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
+ * any number of tab characters.
+ *
+ *
+ * Allowed exceptions:
+ *
+ *
Any line that begins with the '#' character is considered a comment and ignored.
+ *
Blank lines are also ignored
+ *
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.
+ *
+ *
+ * @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()) {
+ final String line = reader.readLine();
+ lineCounter++;
+
+ // ignore lines that start with a # sign - they're comments
+ if (line.startsWith("#") || line.isEmpty()) {
+ continue;
+ }
+
+ // 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;
+ }
+ AbstractUnit.incrementUnitCounter();
+ if (unit instanceof BaseUnit) {
+ AbstractUnit.incrementBaseUnitCounter();
+ }
+ this.addUnit(name, unit);
+ }
+ }
+ }
+ } 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
+ * dimension's name
+ * @param dimension
+ * dimension to add
+ * @throws NullPointerException
+ * if name or dimension is null
+ * @since 2019-03-14
+ */
+ public void addDimension(final String name, final UnitDimension dimension) {
+ this.dimensions.put(Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(dimension, "dimension must not be null."));
+ }
+
+ /**
+ * 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
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public void addPrefix(final String name, final UnitPrefix prefix) {
+ 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
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public void addUnit(final String name, final Unit unit) {
+ this.units.put(Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(unit, "unit must not be null."));
+ }
+
+ /**
+ * Tests if the database has a unit dimension with this name.
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-03-14
+ */
+ public boolean containsDimensionName(final String name) {
+ return this.dimensions.containsKey(name);
+ }
+
+ /**
+ * Tests if the database has a unit with this name, ignoring prefixes
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public boolean containsPrefixlessUnitName(final String name) {
+ return this.units.containsKey(name);
+ }
+
+ /**
+ * Tests if the database has a unit prefix with this name.
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ 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
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public boolean containsUnitName(final String name) {
+ // check for prefixes
+ for (final String prefixName : this.prefixNameSet()) {
+ if (name.startsWith(prefixName))
+ if (this.containsUnitName(name.substring(prefixName.length())))
+ return true;
+ }
+ return this.units.containsKey(name);
+ }
+
+ /**
+ * @return an immutable set of all of the dimension names in this database.
+ * @since 2019-03-14
+ */
+ public Set dimensionNameSet() {
+ return Collections.unmodifiableSet(this.dimensions.keySet());
+ }
+
+ /**
+ * Gets a unit dimension from the database using its name.
+ *
+ * @param name
+ * dimension's name
+ * @return dimension
+ * @since 2019-03-14
+ */
+ public UnitDimension getDimension(final String name) {
+ return this.dimensions.get(name);
+ }
+
+ /**
+ * Gets a unit prefix from the database from its name
+ *
+ * @param name
+ * prefix's name
+ * @return prefix
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public UnitPrefix getPrefix(final String name) {
+ return this.prefixes.get(name);
+ }
+
+ /**
+ * Gets a unit prefix from a prefix expression
+ *
+ * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
+ * another prefix
+ *
+ *
+ * @param expression
+ * expression to input
+ * @return prefix
+ * @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.");
+
+ try {
+ return new DefaultUnitPrefix(Double.parseDouble(expression));
+ } catch (final NumberFormatException e) {
+ if (expression.contains("^")) {
+ final String[] baseAndExponent = expression.split("\\^");
+
+ final double base;
+ try {
+ base = Double.parseDouble(baseAndExponent[0]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Base of exponientation must be a number.");
+ }
+
+ final int exponent;
+ try {
+ exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponent must be an integer.");
+ }
+
+ return new DefaultUnitPrefix(Math.pow(base, exponent));
+ } else {
+ if (!this.containsPrefixName(expression))
+ throw new IllegalArgumentException("Unrecognized prefix name \"" + expression + "\".");
+ return this.getPrefix(expression);
+ }
+ }
+ }
+
+ /**
+ * Gets a unit from the database from its name, ignoring prefixes.
+ *
+ * @param name
+ * unit's name
+ * @return unit
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public Unit getPrefixlessUnit(final String name) {
+ return this.units.get(name);
+ }
+
+ /**
+ * Gets a unit from the database from its name, looking for prefixes.
+ *
+ * @param name
+ * unit's name
+ * @return unit
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public Unit getUnit(final String name) {
+ if (name.contains("^")) {
+ final String[] baseAndExponent = name.split("\\^");
+
+ LinearUnit base;
+ try {
+ base = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(baseAndExponent[0]));
+ } catch (final NumberFormatException e2) {
+ final Unit unit = this.getUnit(baseAndExponent[0]);
+ if (unit instanceof LinearUnit) {
+ base = (LinearUnit) unit;
+ } else if (unit instanceof BaseUnit) {
+ base = ((BaseUnit) unit).asLinearUnit();
+ } else
+ throw new IllegalArgumentException("Base of exponientation must be a linear or base unit.");
+ }
+
+ final int exponent;
+ try {
+ exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponent must be an integer.");
+ }
+
+ final LinearUnit exponentiated = base.toExponent(exponent);
+ if (exponentiated.getConversionFactor() == 1)
+ return exponentiated.getSystem().getBaseUnit(exponentiated.getDimension());
+ else
+ return exponentiated;
+ } else {
+ for (final String prefixName : this.prefixNameSet()) {
+ // check for a prefix
+ if (name.startsWith(prefixName)) {
+ // prefix found! Make sure what comes after it is actually a unit!
+ final String prefixless = name.substring(prefixName.length());
+ if (this.containsUnitName(prefixless)) {
+ // yep, it's a proper prefix! Get the unit!
+ final Unit unit = this.getUnit(prefixless);
+ final UnitPrefix prefix = this.getPrefix(prefixName);
+
+ // Prefixes only work with linear and base units, so make sure it's one of those
+ if (unit instanceof LinearUnit) {
+ final LinearUnit linearUnit = (LinearUnit) unit;
+ return linearUnit.times(prefix.getMultiplier());
+ } else if (unit instanceof BaseUnit) {
+ final BaseUnit baseUnit = (BaseUnit) unit;
+ return baseUnit.times(prefix.getMultiplier());
+ }
+ }
+ }
+ }
+ return this.units.get(name);
+ }
+ }
+
+ /**
+ * Uses the database's unit data to parse an expression into a unit
+ *
+ * The expression is a series of any of the following:
+ *
+ *
The name of a unit, which multiplies or divides the result based on preceding operators
+ *
The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
+ * other is equivalent to multiplication)
+ *
The operator '^' which exponentiates. Exponents must be integers.
+ *
A number which is multiplied or divided
+ *
+ * This method only works with linear units.
+ *
+ * @param expression
+ * expression to parse
+ * @throws IllegalArgumentException
+ * if the expression cannot be parsed
+ * @throws NullPointerException
+ * if any argument is null
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+ public Unit getUnitFromExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // parse the expression
+ // start with an "empty" unit then apply operations on it
+ LinearUnit unit = SI.SI.getBaseUnit(UnitDimension.EMPTY).asLinearUnit();
+ boolean dividing = false;
+
+ // if I'm just creating an alias, just create one instead of going through the parsing process
+ if (!expression.contains(" ") && !expression.contains("*") && !expression.contains("/")
+ && !expression.contains("(") && !expression.contains(")") && !expression.contains("^")) {
+ try {
+ return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(expression));
+ } catch (final NumberFormatException e) {
+ if (!this.containsUnitName(expression))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + expression + "\".");
+ return this.getUnit(expression);
+ }
+ }
+
+ // \\* means "asterisk", * is reserved
+ for (final String part : expression.replaceAll("\\*", " \\* ").replaceAll("/", " / ").replaceAll(" \\^", "\\^")
+ .replaceAll("\\^ ", "\\^").split(" ")) {
+ if ("".equals(part)) {
+ continue;
+ }
+ // "unit1 unit2" is the same as "unit1 * unit2", so multiplication signs do nothing
+ if ("*".equals(part)) {
+ continue;
+ }
+ // When I reach a division sign, don't parse a unit, instead tell myself I'm going to divide the next
+ // thing
+ if ("/".equals(part) || "per".equals(part)) {
+ dividing = true;
+ continue;
+ }
+
+ try {
+ final double partAsNumber = Double.parseDouble(part); // if this works, it's a number
+ // this code should not throw any exceptions, so I'm going to throw AssertionErrors if it does
+ try {
+ if (dividing) {
+ unit = unit.dividedBy(partAsNumber);
+ dividing = false;
+ } else {
+ unit = unit.times(partAsNumber);
+ }
+ } catch (final Exception e) {
+ throw new AssertionError(e);
+ }
+ } catch (final NumberFormatException e) {
+ // it's a unit, try that
+
+ if (part.contains("(") && part.endsWith(")")) {
+ // the unitsfile is looking for a nonlinear unit
+ final String[] unitAndValue = part.split("\\(");
+
+ // this will work because I've checked that it contains a (
+ final String unitName = unitAndValue[0];
+ final String valueStringWithBracket = unitAndValue[unitAndValue.length - 1];
+ final String valueString = valueStringWithBracket.substring(0, valueStringWithBracket.length() - 1);
+ final double value;
+
+ // try to get the value - else throw an error
+ try {
+ value = Double.parseDouble(valueString);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Unparseable value " + valueString);
+ }
+
+ // get this unit in a linear form
+ if (!this.containsPrefixlessUnitName(unitName))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
+ final Unit partUnit = this.getPrefixlessUnit(unitName);
+ final LinearUnit multiplier = partUnit.getBase().times(partUnit.convertToBase(value));
+
+ // finally, add it to the expression
+ if (dividing) {
+ unit = unit.dividedBy(multiplier);
+ dividing = false;
+ } else {
+ unit = unit.times(multiplier);
+ }
+ } else {
+ // check for exponientation
+ if (part.contains("^")) {
+ final String[] valueAndExponent = part.split("\\^");
+ // this will always work because of the contains check
+ final String valueString = valueAndExponent[0];
+ final String exponentString = valueAndExponent[valueAndExponent.length - 1];
+
+ LinearUnit value;
+
+ // first, try to get the value
+ try {
+ final double valueAsNumber = Double.parseDouble(valueString);
+
+ value = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(valueAsNumber);
+ } catch (final NumberFormatException e2) {
+
+ // look for a unit
+ if (!this.containsUnitName(valueString))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + valueString + "\".");
+ final Unit valueUnit = this.getUnit(valueString);
+
+ // try to turn the value into a linear unit
+ if (valueUnit instanceof LinearUnit) {
+ value = (LinearUnit) valueUnit;
+ } else if (valueUnit instanceof BaseUnit) {
+ value = ((BaseUnit) valueUnit).asLinearUnit();
+ } else
+ throw new IllegalArgumentException("Only linear and base units can be exponientated.");
+ }
+
+ // now, try to get the exponent
+ final int exponent;
+ try {
+ exponent = Integer.parseInt(exponentString);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponents must be integers.");
+ }
+
+ final LinearUnit exponientated = value.toExponent(exponent);
+
+ if (dividing) {
+ unit = unit.dividedBy(exponientated);
+ dividing = false;
+ } else {
+ unit = unit.times(exponientated);
+ }
+ } else {
+ // no exponent - look for a unit
+ // the unitsfile is looking for a linear unit
+ if (!this.containsUnitName(part))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
+ Unit other = this.getUnit(part);
+ if (other instanceof BaseUnit) {
+ other = ((BaseUnit) other).asLinearUnit();
+ }
+ if (other instanceof LinearUnit) {
+ if (dividing) {
+ unit = unit.dividedBy((LinearUnit) other);
+ dividing = false;
+ } else {
+ unit = unit.times((LinearUnit) other);
+ }
+ } else
+ throw new IllegalArgumentException(
+ "Only linear or base units can be multiplied and divided. If you want to use a non-linear unit, try the format UNITNAME(VALUE).");
+ }
+ }
+ }
+ }
+
+ // replace conversion-factor-1 linear units with base units
+ // this improves the autogenerated names, allowing them to use derived SI names
+ if (unit != null && unit.getConversionFactor() == 1)
+ return unit.getSystem().getBaseUnit(unit.getDimension());
+ else
+ return unit;
+ }
+
+ /**
+ * @return an immutable set of all of the unit names in this database, ignoring prefixes
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public Set prefixlessUnitNameSet() {
+ return Collections.unmodifiableSet(this.units.keySet());
+ }
+
+ /**
+ * @return an immutable set of all of the prefix names in this database
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public Set prefixNameSet() {
+ return Collections.unmodifiableSet(this.prefixes.keySet());
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/DelegateListModel.java b/src/org/unitConverter/converterGUI/DelegateListModel.java
new file mode 100755
index 0000000..e375126
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/DelegateListModel.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.converterGUI;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.swing.AbstractListModel;
+
+/**
+ * A list model that delegates to a list.
+ *
+ * It is recommended to use the delegate methods in DelegateListModel instead of the delegated list's methods because
+ * the delegate methods handle updating the list.
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+final class DelegateListModel extends AbstractListModel implements List {
+ /**
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private static final long serialVersionUID = 8985494428224810045L;
+
+ /**
+ * The list that this model is a delegate to.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private final List delegate;
+
+ /**
+ * Creates the {@code DelegateListModel}.
+ *
+ * @param delegate
+ * list to delegate
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public DelegateListModel(final List delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean add(final E element) {
+ final int index = this.delegate.size();
+ final boolean success = this.delegate.add(element);
+ this.fireIntervalAdded(this, index, index);
+ return success;
+ }
+
+ @Override
+ public void add(final int index, final E element) {
+ this.delegate.add(index, element);
+ this.fireIntervalAdded(this, index, index);
+ }
+
+ @Override
+ public boolean addAll(final Collection extends E> c) {
+ boolean changed = false;
+ for (final E e : c) {
+ if (this.add(e)) {
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public boolean addAll(final int index, final Collection extends E> c) {
+ for (final E e : c) {
+ this.add(index, e);
+ }
+ return !c.isEmpty(); // Since this is a list, it will always change if c has elements.
+ }
+
+ @Override
+ public void clear() {
+ final int oldSize = this.delegate.size();
+ this.delegate.clear();
+ if (oldSize >= 1) {
+ this.fireIntervalRemoved(this, 0, oldSize - 1);
+ }
+ }
+
+ @Override
+ public boolean contains(final Object elem) {
+ return this.delegate.contains(elem);
+ }
+
+ @Override
+ public boolean containsAll(final Collection> c) {
+ for (final Object e : c) {
+ if (!c.contains(e))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public E get(final int index) {
+ return this.delegate.get(index);
+ }
+
+ @Override
+ public E getElementAt(final int index) {
+ return this.delegate.get(index);
+ }
+
+ @Override
+ public int getSize() {
+ return this.delegate.size();
+ }
+
+ @Override
+ public int indexOf(final Object elem) {
+ return this.delegate.indexOf(elem);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.delegate.isEmpty();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return this.delegate.iterator();
+ }
+
+ @Override
+ public int lastIndexOf(final Object elem) {
+ return this.delegate.lastIndexOf(elem);
+ }
+
+ @Override
+ public ListIterator listIterator() {
+ return this.delegate.listIterator();
+ }
+
+ @Override
+ public ListIterator listIterator(final int index) {
+ return this.delegate.listIterator(index);
+ }
+
+ @Override
+ public E remove(final int index) {
+ final E returnValue = this.delegate.get(index);
+ this.delegate.remove(index);
+ this.fireIntervalRemoved(this, index, index);
+ return returnValue;
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ final int index = this.delegate.indexOf(o);
+ final boolean returnValue = this.delegate.remove(o);
+ this.fireIntervalRemoved(this, index, index);
+ return returnValue;
+ }
+
+ @Override
+ public boolean removeAll(final Collection> c) {
+ boolean changed = false;
+ for (final Object e : c) {
+ if (this.remove(e)) {
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public boolean retainAll(final Collection> c) {
+ final int oldSize = this.size();
+ final boolean returnValue = this.delegate.retainAll(c);
+ this.fireIntervalRemoved(this, this.size(), oldSize - 1);
+ return returnValue;
+ }
+
+ @Override
+ public E set(final int index, final E element) {
+ final E returnValue = this.delegate.get(index);
+ this.delegate.set(index, element);
+ this.fireContentsChanged(this, index, index);
+ return returnValue;
+ }
+
+ @Override
+ public int size() {
+ return this.delegate.size();
+ }
+
+ @Override
+ public List subList(final int fromIndex, final int toIndex) {
+ return this.delegate.subList(fromIndex, toIndex);
+ }
+
+ @Override
+ public Object[] toArray() {
+ return this.delegate.toArray();
+ }
+
+ @Override
+ public T[] toArray(final T[] a) {
+ return this.delegate.toArray(a);
+ }
+
+ @Override
+ public String toString() {
+ return this.delegate.toString();
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/FilterComparator.java b/src/org/unitConverter/converterGUI/FilterComparator.java
new file mode 100755
index 0000000..bebc2df
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/FilterComparator.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.converterGUI;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * A comparator that compares strings using a filter.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+public final class FilterComparator implements Comparator {
+ /**
+ * The filter that the comparator is filtered by.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final String filter;
+ /**
+ * The comparator to use if the arguments are otherwise equal.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final Comparator comparator;
+
+ /**
+ * Creates the {@code FilterComparator}.
+ *
+ * @param filter
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public FilterComparator(final String filter) {
+ this(filter, null);
+ }
+
+ /**
+ * Creates the {@code FilterComparator}.
+ *
+ * @param filter
+ * string to filter by
+ * @param comparator
+ * comparator to fall back to if all else fails, null is compareTo.
+ * @since 2019-01-15
+ * @since v0.1.0
+ * @throws NullPointerException
+ * if filter is null
+ */
+ public FilterComparator(final String filter, final Comparator comparator) {
+ this.filter = Objects.requireNonNull(filter, "filter must not be null.");
+ this.comparator = comparator;
+ }
+
+ @Override
+ public int compare(final String arg0, final String arg1) {
+ // elements that start with the filter always go first
+ if (arg0.startsWith(this.filter) && !arg1.startsWith(this.filter))
+ return -1;
+ else if (!arg0.startsWith(this.filter) && arg1.startsWith(this.filter))
+ return 1;
+
+ // elements that contain the filter but don't start with them go next
+ if (arg0.contains(this.filter) && !arg1.contains(this.filter))
+ return -1;
+ else if (!arg0.contains(this.filter) && !arg1.contains(this.filter))
+ return 1;
+
+ // other elements go last
+ if (this.comparator == null)
+ return arg0.compareTo(arg1);
+ else
+ return this.comparator.compare(arg0, arg1);
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/GridBagBuilder.java b/src/org/unitConverter/converterGUI/GridBagBuilder.java
new file mode 100755
index 0000000..f1229b2
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/GridBagBuilder.java
@@ -0,0 +1,479 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.converterGUI;
+
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+
+/**
+ * A builder for Java's {@link java.awt.GridBagConstraints} class.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+final class GridBagBuilder {
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridx} property.
+ *
+ * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has
+ * gridx=0. The leading edge of a component's display area is its left edge for a horizontal,
+ * left-to-right container and its right edge for a horizontal, right-to-left container. The value
+ * RELATIVE specifies that the component be placed immediately following the component that was added
+ * to the container just before this component was added.
+ *
+ * The default value is RELATIVE. gridx should be a non-negative value.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridy
+ * @see java.awt.ComponentOrientation
+ */
+ private final int gridx;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridy} property.
+ *
+ * Specifies the cell at the top of the component's display area, where the topmost cell has gridy=0.
+ * The value RELATIVE specifies that the component be placed just below the component that was added to
+ * the container just before this component was added.
+ *
+ * The default value is RELATIVE. gridy should be a non-negative value.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridx
+ */
+ private final int gridy;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridwidth} property.
+ *
+ * Specifies the number of cells in a row for the component's display area.
+ *
+ * Use REMAINDER to specify that the component's display area will be from gridx to the
+ * last cell in the row. Use RELATIVE to specify that the component's display area will be from
+ * gridx to the next to the last one in its row.
+ *
+ * gridwidth should be non-negative and the default value is 1.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridheight
+ */
+ private final int gridwidth;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridheight} property.
+ *
+ * Specifies the number of cells in a column for the component's display area.
+ *
+ * Use REMAINDER to specify that the component's display area will be from gridy to the
+ * last cell in the column. Use RELATIVE to specify that the component's display area will be from
+ * gridy to the next to the last one in its column.
+ *
+ * gridheight should be a non-negative value and the default value is 1.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridwidth
+ */
+ private final int gridheight;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code weightx} property.
+ *
+ * Specifies how to distribute extra horizontal space.
+ *
+ * The grid bag layout manager calculates the weight of a column to be the maximum weightx of all the
+ * components in a column. If the resulting layout is smaller horizontally than the area it needs to fill, the extra
+ * space is distributed to each column in proportion to its weight. A column that has a weight of zero receives no
+ * extra space.
+ *
+ * If all the weights are zero, all the extra space appears between the grids of the cell and the left and right
+ * edges.
+ *
+ * The default value of this field is 0. weightx should be a non-negative value.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#weighty
+ */
+ private double weightx;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code weighty} property.
+ *
+ * Specifies how to distribute extra vertical space.
+ *
+ * The grid bag layout manager calculates the weight of a row to be the maximum weighty of all the
+ * components in a row. If the resulting layout is smaller vertically than the area it needs to fill, the extra
+ * space is distributed to each row in proportion to its weight. A row that has a weight of zero receives no extra
+ * space.
+ *
+ * If all the weights are zero, all the extra space appears between the grids of the cell and the top and bottom
+ * edges.
+ *
+ * The default value of this field is 0. weighty should be a non-negative value.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#weightx
+ */
+ private double weighty;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code anchor} property.
+ *
+ * This field is used when the component is smaller than its display area. It determines where, within the display
+ * area, to place the component.
+ *
+ * There are three kinds of possible values: orientation relative, baseline relative and absolute. Orientation
+ * relative values are interpreted relative to the container's component orientation property, baseline relative
+ * values are interpreted relative to the baseline and absolute values are not. The absolute values are:
+ * CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST,
+ * SOUTH, SOUTHWEST, WEST, and NORTHWEST. The orientation
+ * relative values are: PAGE_START, PAGE_END, LINE_START,
+ * LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_START
+ * and LAST_LINE_END. The baseline relative values are: BASELINE,
+ * BASELINE_LEADING, BASELINE_TRAILING, ABOVE_BASELINE,
+ * ABOVE_BASELINE_LEADING, ABOVE_BASELINE_TRAILING, BELOW_BASELINE,
+ * BELOW_BASELINE_LEADING, and BELOW_BASELINE_TRAILING. The default value is
+ * CENTER.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.ComponentOrientation
+ */
+ private int anchor;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code fill} property.
+ *
+ * This field is used when the component's display area is larger than the component's requested size. It determines
+ * whether to resize the component, and if so, how.
+ *
+ * The following values are valid for fill:
+ *
+ *
+ *
NONE: Do not resize the component.
+ *
HORIZONTAL: Make the component wide enough to fill its display area horizontally, but do not
+ * change its height.
+ *
VERTICAL: Make the component tall enough to fill its display area vertically, but do not change
+ * its width.
+ *
BOTH: Make the component fill its display area entirely.
+ *
+ *
+ * The default value is NONE.
+ *
+ * @serial
+ * @see #clone()
+ */
+ private int fill;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code insets} property.
+ *
+ * This field specifies the external padding of the component, the minimum amount of space between the component and
+ * the edges of its display area.
+ *
+ * The default value is new Insets(0, 0, 0, 0).
+ *
+ * @serial
+ * @see #clone()
+ */
+ private Insets insets;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code ipadx} property.
+ *
+ * This field specifies the internal padding of the component, how much space to add to the minimum width of the
+ * component. The width of the component is at least its minimum width plus ipadx pixels.
+ *
+ * The default value is 0.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#ipady
+ */
+ private int ipadx;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code ipady} property.
+ *
+ * This field specifies the internal padding, that is, how much space to add to the minimum height of the component.
+ * The height of the component is at least its minimum height plus ipady pixels.
+ *
+ * The default value is 0.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#ipadx
+ */
+ private int ipady;
+
+ /**
+ * @param gridx
+ * x position
+ * @param gridy
+ * y position
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder(final int gridx, final int gridy) {
+ this(gridx, gridy, 1, 1);
+ }
+
+ /**
+ * @param gridx
+ * x position
+ * @param gridy
+ * y position
+ * @param gridwidth
+ * number of cells occupied horizontally
+ * @param gridheight
+ * number of cells occupied vertically
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight) {
+ this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0);
+ }
+
+ /**
+ * @param gridx
+ * x position
+ * @param gridy
+ * y position
+ * @param gridwidth
+ * number of cells occupied horizontally
+ * @param gridheight
+ * number of cells occupied vertically
+ * @param weightx
+ * @param weighty
+ * @param anchor
+ * @param fill
+ * @param insets
+ * @param ipadx
+ * @param ipady
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight,
+ final double weightx, final double weighty, final int anchor, final int fill, final Insets insets,
+ final int ipadx, final int ipady) {
+ super();
+ this.gridx = gridx;
+ this.gridy = gridy;
+ this.gridwidth = gridwidth;
+ this.gridheight = gridheight;
+ this.weightx = weightx;
+ this.weighty = weighty;
+ this.anchor = anchor;
+ this.fill = fill;
+ this.insets = (Insets) insets.clone();
+ this.ipadx = ipadx;
+ this.ipady = ipady;
+ }
+
+ /**
+ * @return {@code GridBagConstraints} created by this builder
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagConstraints build() {
+ return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, this.gridheight, this.weightx,
+ this.weighty, this.anchor, this.fill, this.insets, this.ipadx, this.ipady);
+ }
+
+ /**
+ * @return anchor
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getAnchor() {
+ return this.anchor;
+ }
+
+ /**
+ * @return fill
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getFill() {
+ return this.fill;
+ }
+
+ /**
+ * @return gridheight
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridheight() {
+ return this.gridheight;
+ }
+
+ /**
+ * @return gridwidth
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridwidth() {
+ return this.gridwidth;
+ }
+
+ /**
+ * @return gridx
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridx() {
+ return this.gridx;
+ }
+
+ /**
+ * @return gridy
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridy() {
+ return this.gridy;
+ }
+
+ /**
+ * @return insets
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public Insets getInsets() {
+ return this.insets;
+ }
+
+ /**
+ * @return ipadx
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getIpadx() {
+ return this.ipadx;
+ }
+
+ /**
+ * @return ipady
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getIpady() {
+ return this.ipady;
+ }
+
+ /**
+ * @return weightx
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public double getWeightx() {
+ return this.weightx;
+ }
+
+ /**
+ * @return weighty
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public double getWeighty() {
+ return this.weighty;
+ }
+
+ /**
+ * @param anchor
+ * anchor to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setAnchor(final int anchor) {
+ this.anchor = anchor;
+ return this;
+ }
+
+ /**
+ * @param fill
+ * fill to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setFill(final int fill) {
+ this.fill = fill;
+ return this;
+ }
+
+ /**
+ * @param insets
+ * insets to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setInsets(final Insets insets) {
+ this.insets = insets;
+ return this;
+ }
+
+ /**
+ * @param ipadx
+ * ipadx to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setIpadx(final int ipadx) {
+ this.ipadx = ipadx;
+ return this;
+ }
+
+ /**
+ * @param ipady
+ * ipady to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setIpady(final int ipady) {
+ this.ipady = ipady;
+ return this;
+ }
+
+ /**
+ * @param weightx
+ * weightx to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setWeightx(final double weightx) {
+ this.weightx = weightx;
+ return this;
+ }
+
+ /**
+ * @param weighty
+ * weighty to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setWeighty(final double weighty) {
+ this.weighty = weighty;
+ return this;
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
new file mode 100755
index 0000000..a70e971
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -0,0 +1,777 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.converterGUI;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Predicate;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFormattedTextField;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+
+import org.unitConverter.UnitsDatabase;
+import org.unitConverter.dimension.StandardDimensions;
+import org.unitConverter.dimension.UnitDimension;
+import org.unitConverter.unit.AbstractUnit;
+import org.unitConverter.unit.NonlinearUnits;
+import org.unitConverter.unit.SI;
+import org.unitConverter.unit.Unit;
+import org.unitConverter.unit.UnitPrefix;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+final class UnitConverterGUI {
+ private static class Presenter {
+ /** The presenter's associated view. */
+ private final View view;
+
+ /** The units known by the program. */
+ private final UnitsDatabase units;
+
+ /** The names of all of the units */
+ private final List unitNames;
+
+ /** The names of all of the units, but filtered */
+ private final DelegateListModel unitNamesFiltered;
+
+ /** The names of all of the prefixes */
+ private final List prefixNames;
+
+ /** The names of all of the prefixes */
+ private final DelegateListModel prefixNamesFiltered;
+
+ private final Comparator prefixNameComparator;
+
+ private int significantFigures = 6;
+
+ /**
+ * Creates the presenter.
+ *
+ * @param view
+ * presenter's associated view
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ Presenter(final View view) {
+ this.view = view;
+
+ // load initial units
+ this.units = new UnitsDatabase();
+ this.units.addUnit("metre", SI.METRE);
+ this.units.addUnit("kilogram", SI.KILOGRAM);
+ this.units.addUnit("gram", SI.KILOGRAM.dividedBy(1000));
+ this.units.addUnit("second", SI.SECOND);
+ this.units.addUnit("ampere", SI.AMPERE);
+ this.units.addUnit("kelvin", SI.KELVIN);
+ this.units.addUnit("mole", SI.MOLE);
+ this.units.addUnit("candela", SI.CANDELA);
+ this.units.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION));
+ this.units.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY));
+ // nonlinear units - must be loaded manually
+ this.units.addUnit("tempCelsius", NonlinearUnits.CELSIUS);
+ this.units.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
+
+ this.units.addAllFromFile(new File("unitsfile.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
+ this.prefixNameComparator = (o1, o2) -> {
+ if (!Presenter.this.units.containsPrefixName(o1))
+ return -1;
+ else if (!Presenter.this.units.containsPrefixName(o2))
+ return 1;
+
+ final UnitPrefix p1 = Presenter.this.units.getPrefix(o1);
+ final UnitPrefix p2 = Presenter.this.units.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.units.prefixlessUnitNameSet());
+ this.unitNames.sort(null); // sorts it using Comparable
+
+ this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixlessUnitNameSet()));
+ this.unitNamesFiltered.sort(null); // sorts it using Comparable
+
+ this.prefixNames = new ArrayList<>(this.units.prefixNameSet());
+ this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator
+
+ this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet()));
+ this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator
+
+ System.out.printf("Successfully loaded %d units (%d base units)", AbstractUnit.getUnitCount(),
+ AbstractUnit.getBaseUnitCount());
+ }
+
+ /**
+ * Runs whenever the convert button is pressed.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 2019-01-26
+ * @since v0.1.0
+ */
+ public final void convert() {
+ final String fromUnitString = this.view.getFromText();
+ final String toUnitString = this.view.getToText();
+
+ // try to parse from
+ final Unit from;
+ try {
+ from = this.units.getUnitFromExpression(fromUnitString);
+ } catch (final IllegalArgumentException e) {
+ 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 {
+ to = this.units.getUnitFromExpression(toUnitString);
+ } catch (final IllegalArgumentException e) {
+ this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage());
+ return;
+ }
+
+ // if I can't convert, leave
+ if (!from.canConvertTo(to)) {
+ this.view.showErrorDialog("Conversion Error",
+ String.format("Cannot convert between %s and %s", fromUnitString, toUnitString));
+ return;
+ }
+
+ value = to.convertFromBase(from.convertToBase(1));
+
+ // round value
+ final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures));
+ String output = bigValue.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);
+ }
+ }
+
+ this.view.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString));
+ }
+
+ /**
+ * Filters the filtered model for units
+ *
+ * @param filter
+ * filter to use
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final void filterFilteredPrefixModel(final Predicate filter) {
+ this.prefixNamesFiltered.clear();
+ for (final String prefixName : this.prefixNames) {
+ if (filter.test(prefixName)) {
+ this.prefixNamesFiltered.add(prefixName);
+ }
+ }
+ }
+
+ /**
+ * Filters the filtered model for units
+ *
+ * @param filter
+ * filter to use
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final void filterFilteredUnitModel(final Predicate filter) {
+ this.unitNamesFiltered.clear();
+ for (final String unitName : this.unitNames) {
+ if (filter.test(unitName)) {
+ this.unitNamesFiltered.add(unitName);
+ }
+ }
+ }
+
+ /**
+ * @return a list model of all of the unit keys
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public final ListModel keyListModel() {
+ return this.unitNamesFiltered;
+ }
+
+ /**
+ * Runs whenever the prefix filter is changed.
+ *
+ * Filters the prefix list then sorts it using a {@code FilterComparator}.
+ *
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public final void prefixFilterUpdated() {
+ final String filter = this.view.getPrefixFilterText();
+ if (filter.equals("")) {
+ this.filterFilteredPrefixModel(t -> true);
+ } else {
+ this.filterFilteredPrefixModel(t -> t.contains(filter));
+ }
+ this.prefixNamesFiltered.sort(new FilterComparator(filter));
+ }
+
+ /**
+ * @return a list model of all of the prefix names
+ * @since 2019-01-15
+ */
+ public final ListModel prefixNameListModel() {
+ return this.prefixNamesFiltered;
+ }
+
+ /**
+ * Runs whenever a prefix is selected in the viewer.
+ *
+ * Shows its information in the text box to the right.
+ *
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public final void prefixSelected() {
+ final int index = this.view.getPrefixListSelection();
+ if (index == -1)
+ return;
+ else {
+ final String prefixName = this.prefixNamesFiltered.get(index);
+ final UnitPrefix prefix = this.units.getPrefix(prefixName);
+
+ this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier()));
+ }
+ }
+
+ /**
+ * @param significantFigures
+ * new value of significantFigures
+ * @since 2019-01-15
+ */
+ public final void setSignificantFigures(final int significantFigures) {
+ this.significantFigures = significantFigures;
+ }
+
+ /**
+ * Runs whenever the unit filter is changed.
+ *
+ * Filters the unit list then sorts it using a {@code FilterComparator}.
+ *
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public final void unitFilterUpdated() {
+ final String filter = this.view.getUnitFilterText();
+ if (filter.equals("")) {
+ this.filterFilteredUnitModel(t -> true);
+ } else {
+ this.filterFilteredUnitModel(t -> t.contains(filter));
+ }
+ this.unitNamesFiltered.sort(new FilterComparator(filter));
+ }
+
+ /**
+ * Runs whenever a unit is selected in the viewer.
+ *
+ * Shows its information in the text box to the right.
+ *
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public void unitNameSelected() {
+ final int index = this.view.getUnitListSelection();
+ if (index == -1)
+ return;
+ else {
+ final String unitName = this.unitNamesFiltered.get(index);
+ final Unit unit = this.units.getUnit(unitName);
+
+ this.view.setUnitTextBoxText(unit.toString());
+ }
+ }
+ }
+
+ private static class View {
+ /** The view's frame. */
+ private final JFrame frame;
+ /** The view's associated presenter. */
+ private final Presenter presenter;
+
+ /** The list of unit names in the unit viewer */
+ private final JList unitNameList;
+ /** The list of prefix names in the prefix viewer */
+ private final JList prefixNameList;
+ /** The unit search box in the unit viewer */
+ private final JTextField unitFilterEntry;
+ /** The text box for unit data in the unit viewer */
+ private final JTextArea unitTextBox;
+ /** The prefix search box in the prefix viewer */
+ private final JTextField prefixFilterEntry;
+ /** The text box for prefix data in the prefix viewer */
+ private final JTextArea prefixTextBox;
+ /** The "From" entry in the conversion panel */
+ private final JTextField fromEntry;
+ /** The "To" entry in the conversion panel */
+ private final JTextField toEntry;
+ /** The output area in the conversion panel */
+ private final JTextArea output;
+
+ /**
+ * Creates the {@code View}.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public View() {
+ this.presenter = new Presenter(this);
+ this.frame = new JFrame("Unit Converter");
+ this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ // create the components
+ this.unitNameList = new JList<>(this.presenter.keyListModel());
+ this.prefixNameList = new JList<>(this.presenter.prefixNameListModel());
+ this.unitFilterEntry = new JTextField();
+ this.unitTextBox = new JTextArea();
+ this.prefixFilterEntry = new JTextField();
+ this.prefixTextBox = new JTextArea();
+ this.fromEntry = new JTextField();
+ this.toEntry = new JTextField();
+ this.output = new JTextArea(2, 32);
+
+ // create more components
+ this.initComponents();
+
+ this.frame.pack();
+ }
+
+ /**
+ * @return text in "From" box in converter panel
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public String getFromText() {
+ return this.fromEntry.getText();
+ }
+
+ /**
+ * @return text in prefix filter
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public String getPrefixFilterText() {
+ return this.prefixFilterEntry.getText();
+ }
+
+ /**
+ * @return index of selected prefix
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public int getPrefixListSelection() {
+ return this.prefixNameList.getSelectedIndex();
+ }
+
+ /**
+ * @return text in "To" box in converter panel
+ * @since 2019-01-26
+ * @since v0.1.0
+ */
+ public String getToText() {
+ return this.toEntry.getText();
+ }
+
+ /**
+ * @return text in unit filter
+ * @see javax.swing.text.JTextComponent#getText()
+ */
+ public String getUnitFilterText() {
+ return this.unitFilterEntry.getText();
+ }
+
+ /**
+ * @return index of selected unit
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public int getUnitListSelection() {
+ return this.unitNameList.getSelectedIndex();
+ }
+
+ /**
+ * Starts up the application.
+ *
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ public final void init() {
+ this.frame.setVisible(true);
+ }
+
+ /**
+ * Initializes the view's components.
+ *
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ private final void initComponents() {
+ final JPanel masterPanel = new JPanel();
+ this.frame.add(masterPanel);
+
+ masterPanel.setLayout(new BorderLayout());
+
+ { // pane with all of the tabs
+ final JTabbedPane masterPane = new JTabbedPane();
+ masterPanel.add(masterPane, BorderLayout.CENTER);
+
+ { // a panel for unit conversion using a selector
+ final JPanel convertUnitPanel = new JPanel();
+ masterPane.addTab("Convert Units", convertUnitPanel);
+
+ convertUnitPanel.setLayout(new BorderLayout());
+
+ { // panel for input part
+ final JPanel inputPanel = new JPanel();
+ convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
+
+ inputPanel.setLayout(new GridLayout(1, 3));
+
+ { // panel for From things
+ final JPanel fromPanel = new JPanel();
+ inputPanel.add(fromPanel);
+
+ fromPanel.setLayout(new BorderLayout());
+
+ { // search box for from
+ final JTextField fromSearch = new JTextField("Search...");
+ fromPanel.add(fromSearch, BorderLayout.PAGE_START);
+ }
+
+ { // list for From units
+ final JList fromList = new JList<>();
+ fromPanel.add(fromList, BorderLayout.CENTER);
+ }
+ }
+
+ { // for dimension selector and arrow that represents conversion
+ final JPanel inBetweenPanel = new JPanel();
+ inputPanel.add(inBetweenPanel);
+
+ inBetweenPanel.setLayout(new BorderLayout());
+
+ { // dimension selector
+ final JComboBox dimensionSelector = new JComboBox<>(
+ new String[] {"Select dimension..."});
+ 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
+ final JPanel toPanel = new JPanel();
+ inputPanel.add(toPanel);
+
+ toPanel.setLayout(new BorderLayout());
+
+ { // search box for to
+ final JTextField toSearch = new JTextField("Search...");
+ toPanel.add(toSearch, BorderLayout.PAGE_START);
+ }
+
+ { // list for To units
+ final JList toList = new JList<>();
+ toPanel.add(toList, BorderLayout.CENTER);
+ }
+ }
+
+ }
+
+ { // 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);
+ }
+
+ { // value to convert
+ final JTextField valueInput = new JFormattedTextField(
+ new DecimalFormat("###############0.################"));
+ valueInputPanel.add(valueInput, BorderLayout.CENTER);
+ }
+ }
+
+ { // button to convert
+ final JButton convertButton = new JButton("Convert");
+ outputPanel.add(convertButton);
+ }
+
+ { // output of conversion
+ final JLabel outputLabel = new JLabel();
+ outputPanel.add(outputLabel);
+ }
+ }
+ }
+
+ { // panel for unit conversion using expressions
+ final JPanel convertExpressionPanel = new JPanel();
+ masterPane.addTab("Convert Unit Expressions", convertExpressionPanel);
+
+ convertExpressionPanel.setLayout(new GridLayout(5, 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.convert());
+ }
+
+ { // output of conversion
+ final JPanel outputPanel = new JPanel();
+ convertExpressionPanel.add(outputPanel);
+
+ outputPanel.setBorder(BorderFactory.createTitledBorder("Output"));
+ outputPanel.setLayout(new GridLayout(1, 1));
+
+ { // output
+ outputPanel.add(this.output);
+ this.output.setEditable(false);
+ }
+ }
+
+ { // panel for specifying precision
+ final JPanel sigDigPanel = new JPanel();
+ convertExpressionPanel.add(sigDigPanel);
+
+ sigDigPanel.setBorder(BorderFactory.createTitledBorder("Significant Digits"));
+
+ { // slider
+ final JSlider sigDigSlider = new JSlider(0, 12);
+ sigDigPanel.add(sigDigSlider);
+
+ sigDigSlider.setMajorTickSpacing(4);
+ sigDigSlider.setMinorTickSpacing(1);
+ sigDigSlider.setSnapToTicks(true);
+ sigDigSlider.setPaintTicks(true);
+ sigDigSlider.setPaintLabels(true);
+
+ sigDigSlider.addChangeListener(
+ e -> this.presenter.setSignificantFigures(sigDigSlider.getValue()));
+ }
+ }
+ }
+
+ { // panel to look up units
+ final JPanel unitLookupPanel = new JPanel();
+ masterPane.addTab("Unit Viewer", unitLookupPanel);
+
+ unitLookupPanel.setLayout(new GridLayout());
+
+ { // panel for listing and searching
+ final JPanel listPanel = new JPanel();
+ unitLookupPanel.add(listPanel);
+
+ listPanel.setLayout(new BorderLayout());
+
+ { // unit search box
+ listPanel.add(this.unitFilterEntry, BorderLayout.PAGE_START);
+ this.unitFilterEntry.addCaretListener(e -> this.presenter.unitFilterUpdated());
+ }
+
+ { // a list of units
+ listPanel.add(new JScrollPane(this.unitNameList), BorderLayout.CENTER);
+ this.unitNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
+ this.unitNameList.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();
+ masterPane.addTab("Prefix Viewer", prefixLookupPanel);
+
+ prefixLookupPanel.setLayout(new GridLayout(1, 2));
+
+ { // panel for listing and seaching
+ final JPanel prefixListPanel = new JPanel();
+ prefixLookupPanel.add(prefixListPanel);
+
+ prefixListPanel.setLayout(new BorderLayout());
+
+ { // prefix search box
+ prefixListPanel.add(this.prefixFilterEntry, BorderLayout.PAGE_START);
+ this.prefixFilterEntry.addCaretListener(e -> this.presenter.prefixFilterUpdated());
+ }
+
+ { // a list of prefixes
+ prefixListPanel.add(new JScrollPane(this.prefixNameList), BorderLayout.CENTER);
+ this.prefixNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
+ this.prefixNameList.addListSelectionListener(e -> {
+ this.presenter.prefixSelected();
+ });
+ }
+ }
+
+ { // the text box for prefix's toString
+ prefixLookupPanel.add(this.prefixTextBox);
+ this.unitTextBox.setEditable(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the text in the output of the conversion panel.
+ *
+ * @param text
+ * text to set
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public void setOutputText(final String text) {
+ this.output.setText(text);
+ }
+
+ /**
+ * Sets the text of the prefix text box.
+ *
+ * @param text
+ * text to set
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public void setPrefixTextBoxText(final String text) {
+ this.prefixTextBox.setText(text);
+ }
+
+ /**
+ * Sets the text of the unit text box.
+ *
+ * @param t
+ * text to set
+ * @see javax.swing.text.JTextComponent#setText(java.lang.String)
+ */
+ public void setUnitTextBoxText(final String t) {
+ this.unitTextBox.setText(t);
+ }
+
+ /**
+ * Shows an error dialog.
+ *
+ * @param title
+ * title of dialog
+ * @param message
+ * message in dialog
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public void showErrorDialog(final String title, final String 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/converterGUI/package-info.java b/src/org/unitConverter/converterGUI/package-info.java
new file mode 100644
index 0000000..d899f97
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * All classes that work to convert units.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-25
+ */
+package org.unitConverter.converterGUI;
\ No newline at end of file
diff --git a/src/org/unitConverter/dimension/BaseDimension.java b/src/org/unitConverter/dimension/BaseDimension.java
new file mode 100755
index 0000000..5e3ddad
--- /dev/null
+++ b/src/org/unitConverter/dimension/BaseDimension.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.dimension;
+
+/**
+ * A base dimension that makes up {@code UnitDimension} objects.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+public interface BaseDimension {
+ /**
+ * @return the dimension's name
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ String getName();
+
+ /**
+ * @return a short string (usually one character) that represents this base dimension
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ String getSymbol();
+}
diff --git a/src/org/unitConverter/dimension/OtherBaseDimension.java b/src/org/unitConverter/dimension/OtherBaseDimension.java
new file mode 100755
index 0000000..8aea2b9
--- /dev/null
+++ b/src/org/unitConverter/dimension/OtherBaseDimension.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.dimension;
+
+import java.util.Objects;
+
+/**
+ * Non-SI base dimensions.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public enum OtherBaseDimension implements BaseDimension {
+ INFORMATION("Info"), CURRENCY("$$");
+
+ /** The dimension's symbol */
+ private final String symbol;
+
+ /**
+ * Creates the {@code SIBaseDimension}.
+ *
+ * @param symbol
+ * dimension's symbol
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+ private OtherBaseDimension(final String symbol) {
+ this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
+ }
+
+ @Override
+ public String getName() {
+ return this.toString();
+ }
+
+ @Override
+ public String getSymbol() {
+ return this.symbol;
+ }
+}
diff --git a/src/org/unitConverter/dimension/SIBaseDimension.java b/src/org/unitConverter/dimension/SIBaseDimension.java
new file mode 100755
index 0000000..c459963
--- /dev/null
+++ b/src/org/unitConverter/dimension/SIBaseDimension.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.dimension;
+
+import java.util.Objects;
+
+/**
+ * The seven base dimensions that make up the SI.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+public enum SIBaseDimension implements BaseDimension {
+ LENGTH("L"), MASS("M"), TIME("T"), ELECTRIC_CURRENT("I"), TEMPERATURE("\u0398"), // u0398 is the theta symbol
+ QUANTITY("N"), LUMINOUS_INTENSITY("J");
+
+ /** The dimension's symbol */
+ private final String symbol;
+
+ /**
+ * Creates the {@code SIBaseDimension}.
+ *
+ * @param symbol
+ * dimension's symbol
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+ private SIBaseDimension(final String symbol) {
+ this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
+ }
+
+ @Override
+ public String getName() {
+ return this.toString();
+ }
+
+ @Override
+ public String getSymbol() {
+ return this.symbol;
+ }
+
+}
diff --git a/src/org/unitConverter/dimension/StandardDimensions.java b/src/org/unitConverter/dimension/StandardDimensions.java
new file mode 100755
index 0000000..4b1b814
--- /dev/null
+++ b/src/org/unitConverter/dimension/StandardDimensions.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.dimension;
+
+/**
+ * All of the dimensions that are used by the SI.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+public final class StandardDimensions {
+ // base dimensions
+ public static final UnitDimension EMPTY = UnitDimension.EMPTY;
+ public static final UnitDimension LENGTH = UnitDimension.getBase(SIBaseDimension.LENGTH);
+ public static final UnitDimension MASS = UnitDimension.getBase(SIBaseDimension.MASS);
+ public static final UnitDimension TIME = UnitDimension.getBase(SIBaseDimension.TIME);
+ public static final UnitDimension ELECTRIC_CURRENT = UnitDimension.getBase(SIBaseDimension.ELECTRIC_CURRENT);
+ public static final UnitDimension TEMPERATURE = UnitDimension.getBase(SIBaseDimension.TEMPERATURE);
+ public static final UnitDimension QUANTITY = UnitDimension.getBase(SIBaseDimension.QUANTITY);
+ public static final UnitDimension LUMINOUS_INTENSITY = UnitDimension.getBase(SIBaseDimension.LUMINOUS_INTENSITY);
+ public static final UnitDimension INFORMATION = UnitDimension.getBase(OtherBaseDimension.INFORMATION);
+ public static final UnitDimension CURRENCY = UnitDimension.getBase(OtherBaseDimension.CURRENCY);
+ // derived dimensions without named SI units
+ public static final UnitDimension AREA = LENGTH.times(LENGTH);
+
+ public static final UnitDimension VOLUME = AREA.times(LENGTH);
+ public static final UnitDimension VELOCITY = LENGTH.dividedBy(TIME);
+ public static final UnitDimension ACCELERATION = VELOCITY.dividedBy(TIME);
+ public static final UnitDimension WAVENUMBER = EMPTY.dividedBy(LENGTH);
+ public static final UnitDimension MASS_DENSITY = MASS.dividedBy(VOLUME);
+ public static final UnitDimension SURFACE_DENSITY = MASS.dividedBy(AREA);
+ public static final UnitDimension SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
+ public static final UnitDimension CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
+ public static final UnitDimension MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
+ public static final UnitDimension CONCENTRATION = QUANTITY.dividedBy(VOLUME);
+ public static final UnitDimension MASS_CONCENTRATION = CONCENTRATION.times(MASS);
+ public static final UnitDimension LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
+ public static final UnitDimension REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
+ public static final UnitDimension REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
+ public static final UnitDimension ANGLE = LENGTH.dividedBy(LENGTH);
+ public static final UnitDimension SOLID_ANGLE = AREA.dividedBy(AREA);
+ // derived dimensions with named SI units
+ public static final UnitDimension FREQUENCY = EMPTY.dividedBy(TIME);
+
+ public static final UnitDimension FORCE = MASS.times(ACCELERATION);
+ public static final UnitDimension ENERGY = FORCE.times(LENGTH);
+ public static final UnitDimension POWER = ENERGY.dividedBy(TIME);
+ public static final UnitDimension ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
+ public static final UnitDimension VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
+ public static final UnitDimension CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
+ public static final UnitDimension ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
+ public static final UnitDimension ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
+ public static final UnitDimension MAGNETIC_FLUX = VOLTAGE.times(TIME);
+ public static final UnitDimension MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
+ public static final UnitDimension INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
+ public static final UnitDimension LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
+ public static final UnitDimension ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
+ public static final UnitDimension SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
+ public static final UnitDimension CATALYTIC_ACTIVITY = QUANTITY.dividedBy(TIME);
+
+ // You may NOT get StandardDimensions instances!
+ private StandardDimensions() {
+ throw new AssertionError();
+ }
+}
diff --git a/src/org/unitConverter/dimension/UnitDimension.java b/src/org/unitConverter/dimension/UnitDimension.java
new file mode 100755
index 0000000..dbeaeff
--- /dev/null
+++ b/src/org/unitConverter/dimension/UnitDimension.java
@@ -0,0 +1,241 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.dimension;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * An object that represents what a unit measures, like length, mass, area, energy, etc.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+public final class UnitDimension {
+ /**
+ * The unit dimension where every exponent is zero
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ public static final UnitDimension EMPTY = new UnitDimension(new HashMap<>());
+
+ /**
+ * Gets an UnitDimension that has 1 of a certain dimension and nothing else
+ *
+ * @param dimension
+ * dimension to get
+ * @return unit dimension
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+ public static final UnitDimension getBase(final BaseDimension dimension) {
+ final Map map = new HashMap<>();
+ map.put(dimension, 1);
+ return new UnitDimension(map);
+ }
+
+ /**
+ * The base dimensions that make up this dimension.
+ *
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+ final Map exponents;
+
+ /**
+ * Creates the {@code UnitDimension}.
+ *
+ * @param exponents
+ * base dimensions that make up this dimension
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+ private UnitDimension(final Map exponents) {
+ this.exponents = new HashMap<>(exponents);
+ }
+
+ /**
+ * Divides this dimension by another
+ *
+ * @param other
+ * other dimension
+ * @return quotient of two dimensions
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+ public UnitDimension dividedBy(final UnitDimension other) {
+ final Map map = new HashMap<>(this.exponents);
+
+ for (final BaseDimension key : other.exponents.keySet()) {
+ if (map.containsKey(key)) {
+ // add the dimensions
+ map.put(key, map.get(key) - other.exponents.get(key));
+ } else {
+ map.put(key, -other.exponents.get(key));
+ }
+ }
+ return new UnitDimension(map);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof UnitDimension))
+ return false;
+ final UnitDimension other = (UnitDimension) obj;
+
+ // anything with a value of 0 is equal to a nonexistent value
+ for (final BaseDimension b : this.getBaseSet()) {
+ if (this.exponents.get(b) != other.exponents.get(b))
+ if (!(this.exponents.get(b) == 0 && !other.exponents.containsKey(b)))
+ return false;
+ }
+ for (final BaseDimension b : other.getBaseSet()) {
+ if (this.exponents.get(b) != other.exponents.get(b))
+ if (!(other.exponents.get(b) == 0 && !this.exponents.containsKey(b)))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return a set of all of the base dimensions with non-zero exponents that make up this dimension.
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ public final Set getBaseSet() {
+ final Set dimensions = new HashSet<>();
+
+ // add all dimensions with a nonzero exponent - they shouldn't be there in the first place
+ for (final BaseDimension dimension : this.exponents.keySet()) {
+ if (!this.exponents.get(dimension).equals(0)) {
+ dimensions.add(dimension);
+ }
+ }
+
+ return dimensions;
+ }
+
+ /**
+ * Gets the exponent for a specific dimension.
+ *
+ * @param dimension
+ * dimension to check
+ * @return exponent for that dimension
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ public int getExponent(final BaseDimension dimension) {
+ return this.exponents.getOrDefault(dimension, 0);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.exponents);
+ }
+
+ /**
+ * @return true if this dimension is a base, i.e. it has one exponent of one and no other nonzero exponents
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public boolean isBase() {
+ int oneCount = 0;
+ boolean twoOrMore = false; // has exponents of 2 or more
+ for (final BaseDimension b : this.getBaseSet()) {
+ if (this.exponents.get(b) == 1) {
+ oneCount++;
+ } else if (this.exponents.get(b) != 0) {
+ twoOrMore = true;
+ }
+ }
+ return (oneCount == 0 || oneCount == 1) && !twoOrMore;
+ }
+
+ /**
+ * Multiplies this dimension by another
+ *
+ * @param other
+ * other dimension
+ * @return product of two dimensions
+ * @since 2018-12-11
+ * @since v0.1.0
+ */
+ public UnitDimension times(final UnitDimension other) {
+ final Map map = new HashMap<>(this.exponents);
+
+ for (final BaseDimension key : other.exponents.keySet()) {
+ if (map.containsKey(key)) {
+ // add the dimensions
+ map.put(key, map.get(key) + other.exponents.get(key));
+ } else {
+ map.put(key, other.exponents.get(key));
+ }
+ }
+ return new UnitDimension(map);
+ }
+
+ /**
+ * Returns this dimension, but to an exponent
+ *
+ * @param exp
+ * exponent
+ * @return result of exponientation
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public UnitDimension toExponent(final int exp) {
+ final Map map = new HashMap<>(this.exponents);
+ for (final BaseDimension key : this.exponents.keySet()) {
+ map.put(key, this.getExponent(key) * exp);
+ }
+ return new UnitDimension(map);
+ }
+
+ @Override
+ public String toString() {
+ final List positiveStringComponents = new ArrayList<>();
+ final List negativeStringComponents = new ArrayList<>();
+
+ // for each base dimension that makes up this dimension, add it and its exponent
+ for (final BaseDimension dimension : this.getBaseSet()) {
+ final int exponent = this.exponents.get(dimension);
+ if (exponent > 0) {
+ positiveStringComponents.add(String.format("%s^%d", dimension.getSymbol(), exponent));
+ } else if (exponent < 0) {
+ negativeStringComponents.add(String.format("%s^%d", dimension.getSymbol(), -exponent));
+ }
+ }
+
+ final String positiveString = positiveStringComponents.isEmpty() ? "1"
+ : String.join(" ", positiveStringComponents);
+ final String negativeString = negativeStringComponents.isEmpty() ? ""
+ : " / " + String.join(" ", negativeStringComponents);
+
+ return positiveString + negativeString;
+ }
+}
diff --git a/src/org/unitConverter/dimension/UnitDimensionTest.java b/src/org/unitConverter/dimension/UnitDimensionTest.java
new file mode 100755
index 0000000..3b09610
--- /dev/null
+++ b/src/org/unitConverter/dimension/UnitDimensionTest.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.dimension;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.unitConverter.dimension.StandardDimensions.AREA;
+import static org.unitConverter.dimension.StandardDimensions.ENERGY;
+import static org.unitConverter.dimension.StandardDimensions.LENGTH;
+import static org.unitConverter.dimension.StandardDimensions.MASS;
+import static org.unitConverter.dimension.StandardDimensions.MASS_DENSITY;
+import static org.unitConverter.dimension.StandardDimensions.QUANTITY;
+import static org.unitConverter.dimension.StandardDimensions.TIME;
+import static org.unitConverter.dimension.StandardDimensions.VOLUME;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link UnitDimension}.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+class UnitDimensionTest {
+ /**
+ * Tests {@link UnitDimension#equals}
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ @Test
+ void testEquals() {
+ assertEquals(LENGTH, LENGTH);
+ assertFalse(LENGTH.equals(QUANTITY));
+ }
+
+ /**
+ * Tests {@code UnitDimension}'s exponentiation
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ @Test
+ void testExponents() {
+ assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
+ assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
+ }
+
+ /**
+ * Tests {@code UnitDimension}'s multiplication and division.
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ @Test
+ void testMultiplicationAndDivision() {
+ assertEquals(AREA, LENGTH.times(LENGTH));
+ assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME));
+ assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME));
+ assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME));
+ }
+}
diff --git a/src/org/unitConverter/dimension/package-info.java b/src/org/unitConverter/dimension/package-info.java
new file mode 100755
index 0000000..db363df
--- /dev/null
+++ b/src/org/unitConverter/dimension/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * Everything to do with what a unit measures, or its dimension.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ */
+package org.unitConverter.dimension;
\ No newline at end of file
diff --git a/src/org/unitConverter/package-info.java b/src/org/unitConverter/package-info.java
new file mode 100644
index 0000000..4f51ad0
--- /dev/null
+++ b/src/org/unitConverter/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * A program that converts units.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-25
+ */
+package org.unitConverter;
\ No newline at end of file
diff --git a/src/org/unitConverter/unit/AbstractUnit.java b/src/org/unitConverter/unit/AbstractUnit.java
new file mode 100644
index 0000000..6088960
--- /dev/null
+++ b/src/org/unitConverter/unit/AbstractUnit.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import java.util.Objects;
+
+import org.unitConverter.dimension.UnitDimension;
+
+/**
+ * The default abstract implementation of the {@code Unit} interface.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+public abstract class AbstractUnit implements Unit {
+ /**
+ * The number of units created, including base units.
+ *
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ private static long unitCount = 0;
+
+ /**
+ * The number of base units created.
+ *
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ private static long baseUnitCount = 0;
+
+ /**
+ * @return number of base units created
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ public static final long getBaseUnitCount() {
+ return baseUnitCount;
+ }
+
+ /**
+ * @return number of units created
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ public static final long getUnitCount() {
+ return unitCount;
+ }
+
+ /**
+ * Increments the number of base units.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public static final void incrementBaseUnitCounter() {
+ baseUnitCount++;
+ }
+
+ /**
+ * Increments the number of units.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public static final void incrementUnitCounter() {
+ unitCount++;
+ }
+
+ /**
+ * The dimension, or what the unit measures.
+ *
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ private final UnitDimension dimension;
+
+ /**
+ * The unit's base unit. Values converted by {@code convertFromBase} and {@code convertToBase} are expressed in this
+ * unit.
+ *
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ private final BaseUnit base;
+
+ /**
+ * The system that this unit is a part of.
+ *
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ private final UnitSystem system;
+
+ /**
+ * Creates the {@code AbstractUnit}.
+ *
+ * @param base
+ * unit's base
+ * @throws NullPointerException
+ * if name, symbol or base is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public AbstractUnit(final BaseUnit base) {
+ this.base = Objects.requireNonNull(base, "base must not be null.");
+ this.dimension = this.base.getDimension();
+ this.system = this.base.getSystem();
+ }
+
+ /**
+ * Creates the {@code AbstractUnit} using a unique dimension. This constructor is for making base units and should
+ * only be used by {@code BaseUnit}.
+ *
+ * @param dimension
+ * dimension measured by unit
+ * @param system
+ * system that unit is a part of
+ * @throws AssertionError
+ * if this constructor is not run by {@code BaseUnit} or a subclass
+ * @throws NullPointerException
+ * if name, symbol or dimension is null
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ AbstractUnit(final UnitDimension dimension, final UnitSystem system) {
+ // try to set this as a base unit
+ if (this instanceof BaseUnit) {
+ this.base = (BaseUnit) this;
+ } else
+ throw new AssertionError();
+
+ this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
+ this.system = Objects.requireNonNull(system, "system must not be null.");
+ }
+
+ @Override
+ public final BaseUnit getBase() {
+ return this.base;
+ }
+
+ @Override
+ public final UnitDimension getDimension() {
+ return this.dimension;
+ }
+
+ @Override
+ public final UnitSystem getSystem() {
+ return this.system;
+ }
+
+ // TODO document and revise units' toString methods
+ @Override
+ public String toString() {
+ return String.format("%s-derived unit of dimension %s", this.getSystem(), this.getDimension());
+ }
+}
diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
new file mode 100755
index 0000000..1f0c825
--- /dev/null
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -0,0 +1,180 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import java.util.Objects;
+
+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.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+public final class BaseUnit extends AbstractUnit {
+ /**
+ * Is this unit a full base (i.e. m, s, ... but not N, J, ...)
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final boolean isFullBase;
+
+ /**
+ * Creates the {@code BaseUnit}.
+ *
+ * @param dimension
+ * dimension measured by unit
+ * @param system
+ * system that unit is a part of
+ * @param name
+ * name of unit
+ * @param symbol
+ * symbol of unit
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ BaseUnit(final UnitDimension dimension, final UnitSystem system) {
+ super(dimension, system);
+ this.isFullBase = dimension.isBase();
+ }
+
+ /**
+ * @return this unit as a {@code LinearUnit}
+ * @since 2019-01-25
+ * @since v0.1.0
+ */
+ public LinearUnit asLinearUnit() {
+ return this.times(1);
+ }
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value;
+ }
+
+ /**
+ * Divides this unit by another unit.
+ *
+ * @param other
+ * unit to divide by
+ * @return quotient of two units
+ * @throws IllegalArgumentException
+ * if this unit's system is not other's system
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public BaseUnit dividedBy(final BaseUnit other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ if (!this.getSystem().equals(other.getSystem()))
+ throw new IllegalArgumentException("Incompatible base units for division.");
+ return new BaseUnit(this.getDimension().dividedBy(other.getDimension()), this.getSystem());
+ }
+
+ /**
+ * Divides this unit by a divisor
+ *
+ * @param divisor
+ * amount to divide by
+ * @return quotient
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final double divisor) {
+ return new LinearUnit(this, 1 / divisor);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof BaseUnit))
+ return false;
+ final BaseUnit other = (BaseUnit) obj;
+ return Objects.equals(this.getSystem(), other.getSystem())
+ && Objects.equals(this.getDimension(), other.getDimension());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = result * prime + this.getSystem().hashCode();
+ result = result * prime + this.getDimension().hashCode();
+ return result;
+ }
+
+ /**
+ * Multiplies this unit by another unit.
+ *
+ * @param other
+ * unit to multiply by
+ * @return product of two units
+ * @throws IllegalArgumentException
+ * if this unit's system is not other's system
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public BaseUnit times(final BaseUnit other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ if (!this.getSystem().equals(other.getSystem()))
+ throw new IllegalArgumentException("Incompatible base units for multiplication.");
+ return new BaseUnit(this.getDimension().times(other.getDimension()), this.getSystem());
+ }
+
+ /**
+ * Multiplies this unit by a multiplier.
+ *
+ * @param multiplier
+ * amount to multiply by
+ * @return product
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit times(final double multiplier) {
+ return new LinearUnit(this, multiplier);
+ }
+
+ /**
+ * Returns this unit, but to an exponent.
+ *
+ * @param exponent
+ * exponent
+ * @return result of exponentiation
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public BaseUnit toExponent(final int exponent) {
+ return this.getSystem().getBaseUnit(this.getDimension().toExponent(exponent));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s base unit of%s dimension %s", this.getSystem(), this.isFullBase ? " base" : "",
+ this.getDimension());
+ }
+}
diff --git a/src/org/unitConverter/unit/DefaultUnitPrefix.java b/src/org/unitConverter/unit/DefaultUnitPrefix.java
new file mode 100755
index 0000000..c0e8dcc
--- /dev/null
+++ b/src/org/unitConverter/unit/DefaultUnitPrefix.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import java.util.Objects;
+
+/**
+ * The default implementation of {@code UnitPrefix}, which contains a multiplier and nothing else.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public final class DefaultUnitPrefix implements UnitPrefix {
+ private final double multiplier;
+
+ /**
+ * Creates the {@code DefaultUnitPrefix}.
+ *
+ * @param multiplier
+ * @since 2019-01-14
+ */
+ public DefaultUnitPrefix(final double multiplier) {
+ this.multiplier = multiplier;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof DefaultUnitPrefix))
+ return false;
+ final DefaultUnitPrefix other = (DefaultUnitPrefix) obj;
+ return Double.doubleToLongBits(this.multiplier) == Double.doubleToLongBits(other.multiplier);
+ }
+
+ @Override
+ public double getMultiplier() {
+ return this.multiplier;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.multiplier);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Unit prefix equal to %s", this.multiplier);
+ }
+}
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
new file mode 100644
index 0000000..ab46f1e
--- /dev/null
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -0,0 +1,184 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import java.util.Objects;
+
+import org.unitConverter.dimension.UnitDimension;
+
+/**
+ * A unit that is equal to a certain number multiplied by its base.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+public final class LinearUnit extends AbstractUnit {
+ /**
+ * The value of one of this unit in this unit's base unit
+ *
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ private final double conversionFactor;
+
+ /**
+ *
+ * Creates the {@code LinearUnit}.
+ *
+ * @param base
+ * unit's base
+ * @param conversionFactor
+ * value of one of this unit in its base
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ LinearUnit(final BaseUnit base, final double conversionFactor) {
+ super(base);
+ this.conversionFactor = conversionFactor;
+ }
+
+ /**
+ * Creates the {@code LinearUnit} as a base unit.
+ *
+ * @param dimension
+ * dimension measured by unit
+ * @param system
+ * system unit is part of
+ * @since 2019-01-25
+ * @since v0.1.0
+ */
+ LinearUnit(final UnitDimension dimension, final UnitSystem system, final double conversionFactor) {
+ super(dimension, system);
+ this.conversionFactor = conversionFactor;
+ }
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value / this.getConversionFactor();
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value * this.getConversionFactor();
+ }
+
+ /**
+ * Divides this unit by a scalar.
+ *
+ * @param divisor
+ * scalar to divide by
+ * @return quotient
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final double divisor) {
+ return new LinearUnit(this.getBase(), this.getConversionFactor() / divisor);
+ }
+
+ /**
+ * Divides this unit by another unit.
+ *
+ * @param other
+ * unit to divide by
+ * @return quotient of two units
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final LinearUnit other) {
+ Objects.requireNonNull(other, "other must not be null");
+ final BaseUnit base = this.getBase().dividedBy(other.getBase());
+ return new LinearUnit(base, this.getConversionFactor() / other.getConversionFactor());
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof LinearUnit))
+ return false;
+ final LinearUnit other = (LinearUnit) obj;
+ return Objects.equals(this.getBase(), other.getBase())
+ && Objects.equals(this.getConversionFactor(), other.getConversionFactor());
+ }
+
+ /**
+ * @return conversionFactor
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public final double getConversionFactor() {
+ return this.conversionFactor;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = result * prime + this.getBase().hashCode();
+ result = result * prime + Double.hashCode(this.getConversionFactor());
+ return result;
+ }
+
+ /**
+ * Multiplies this unit by a scalar.
+ *
+ * @param multiplier
+ * scalar to multiply by
+ * @return product
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit times(final double multiplier) {
+ return new LinearUnit(this.getBase(), this.getConversionFactor() * multiplier);
+ }
+
+ /**
+ * Multiplies this unit by another unit.
+ *
+ * @param other
+ * unit to multiply by=
+ * @return product of two units
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public LinearUnit times(final LinearUnit other) {
+ Objects.requireNonNull(other, "other must not be null");
+ final BaseUnit base = this.getBase().times(other.getBase());
+ return new LinearUnit(base, this.getConversionFactor() * other.getConversionFactor());
+ }
+
+ /**
+ * Returns this unit but to an exponent.
+ *
+ * @param exponent
+ * exponent to exponientate unit to
+ * @return exponientated unit
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public LinearUnit toExponent(final int exponent) {
+ return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + String.format(" (equal to %s * base)", this.getConversionFactor());
+ }
+}
diff --git a/src/org/unitConverter/unit/NonlinearUnits.java b/src/org/unitConverter/unit/NonlinearUnits.java
new file mode 100755
index 0000000..e47c28f
--- /dev/null
+++ b/src/org/unitConverter/unit/NonlinearUnits.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+/**
+ * Some major nonlinear units.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public final class NonlinearUnits {
+ public static final Unit CELSIUS = new AbstractUnit(SI.KELVIN) {
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value - 273.15;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value + 273.15;
+ }
+ };
+
+ public static final Unit FAHRENHEIT = new AbstractUnit(SI.KELVIN) {
+
+ @Override
+ public double convertFromBase(final double value) {
+ return 1.8 * value - 459.67;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return (value + 459.67) / 1.8;
+ }
+ };
+
+ // You may NOT get a NonlinearUnits instance.
+ private NonlinearUnits() {
+ throw new AssertionError();
+ }
+}
diff --git a/src/org/unitConverter/unit/SI.java b/src/org/unitConverter/unit/SI.java
new file mode 100644
index 0000000..46e6ff1
--- /dev/null
+++ b/src/org/unitConverter/unit/SI.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import org.unitConverter.dimension.StandardDimensions;
+import org.unitConverter.dimension.UnitDimension;
+
+/**
+ * The SI, which holds all SI units
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+public enum SI implements UnitSystem {
+ SI;
+
+ /**
+ * This system's base units.
+ *
+ * @since 2019-01-25
+ * @since v0.1.0
+ */
+ private static final Set baseUnits = new HashSet<>();
+
+ // base units
+ public static final BaseUnit METRE = SI.getBaseUnit(StandardDimensions.LENGTH);
+ public static final BaseUnit KILOGRAM = SI.getBaseUnit(StandardDimensions.MASS);
+ public static final BaseUnit SECOND = SI.getBaseUnit(StandardDimensions.TIME);
+ public static final BaseUnit AMPERE = SI.getBaseUnit(StandardDimensions.ELECTRIC_CURRENT);
+ public static final BaseUnit KELVIN = SI.getBaseUnit(StandardDimensions.TEMPERATURE);
+ public static final BaseUnit MOLE = SI.getBaseUnit(StandardDimensions.QUANTITY);
+ public static final BaseUnit CANDELA = SI.getBaseUnit(StandardDimensions.LUMINOUS_INTENSITY);
+
+ @Override
+ public BaseUnit getBaseUnit(final UnitDimension dimension) {
+ // try to find an existing unit before creating a new one
+
+ Objects.requireNonNull(dimension, "dimension must not be null.");
+ for (final BaseUnit unit : baseUnits) {
+ // it will be equal since the conditions for equality are dimension and system,
+ // and system is always SI.
+ if (unit.getDimension().equals(dimension))
+ return unit;
+ }
+ // could not find an existing base unit
+ final BaseUnit unit = new BaseUnit(dimension, this);
+ baseUnits.add(unit);
+ return unit;
+ }
+
+ @Override
+ public String getName() {
+ return "SI";
+ }
+}
diff --git a/src/org/unitConverter/unit/SIPrefix.java b/src/org/unitConverter/unit/SIPrefix.java
new file mode 100755
index 0000000..31d7ff2
--- /dev/null
+++ b/src/org/unitConverter/unit/SIPrefix.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+/**
+ * The SI prefixes.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public enum SIPrefix implements UnitPrefix {
+ DECA(10), HECTO(100), KILO(1e3), MEGA(1e6), GIGA(1e9), TERA(1e12), PETA(1e15), EXA(1e18), ZETTA(1e21), YOTTA(
+ 1e24), DECI(0.1), CENTI(0.01), MILLI(
+ 1e-3), MICRO(1e-6), NANO(1e-9), PICO(1e-12), FEMTO(1e-15), ATTO(1e-18), ZEPTO(1e-21), YOCTO(1e-24);
+
+ private final double multiplier;
+
+ /**
+ * Creates the {@code SIPrefix}.
+ *
+ * @param multiplier
+ * prefix's multiplier
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private SIPrefix(final double multiplier) {
+ this.multiplier = multiplier;
+ }
+
+ /**
+ * @return value
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ @Override
+ public final double getMultiplier() {
+ return this.multiplier;
+ }
+}
diff --git a/src/org/unitConverter/unit/Unit.java b/src/org/unitConverter/unit/Unit.java
new file mode 100755
index 0000000..86fc5a2
--- /dev/null
+++ b/src/org/unitConverter/unit/Unit.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import java.util.Objects;
+
+import org.unitConverter.dimension.UnitDimension;
+
+/**
+ * A unit that has an associated base unit, and can convert a value expressed in it to and from that base.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+public interface Unit {
+ /**
+ * Checks if a value expressed in this unit can be converted to a value expressed in {@code other}
+ *
+ * @param other
+ * unit to test with
+ * @return true if the units are compatible
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ default boolean canConvertTo(final Unit other) {
+ return Objects.equals(this.getBase(), other.getBase());
+ }
+
+ /**
+ * Converts from a value expressed in this unit's base unit to a value expressed in this unit.
+ *
+ * This must be the inverse of {@code convertToBase}, so {@code convertFromBase(convertToBase(value))} must be equal
+ * to {@code value} for any value, ignoring precision loss by roundoff error.
+ *
+ *
+ * If this unit is a base unit, this method should return {@code value}.
+ *
+ *
+ * @param value
+ * value expressed in base unit
+ * @return value expressed in this unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ double convertFromBase(double value);
+
+ /**
+ * Converts from a value expressed in this unit to a value expressed in this unit's base unit.
+ *
+ * This must be the inverse of {@code convertFromBase}, so {@code convertToBase(convertFromBase(value))} must be
+ * equal to {@code value} for any value, ignoring precision loss by roundoff error.
+ *
+ *
+ * If this unit is a base unit, this method should return {@code value}.
+ *
+ *
+ * @param value
+ * value expressed in this unit
+ * @return value expressed in base unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ double convertToBase(double value);
+
+ /**
+ *
+ * Returns the base unit associated with this unit.
+ *
+ *
+ * The dimension of this unit must be equal to the dimension of the returned unit.
+ *
+ *
+ * If this unit is a base unit, this method should return this unit.\
+ *
+ *
+ * @return base unit associated with this unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ BaseUnit getBase();
+
+ /**
+ * @return dimension measured by this unit
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ UnitDimension getDimension();
+
+ /**
+ * @return system that this unit is a part of
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ UnitSystem getSystem();
+}
diff --git a/src/org/unitConverter/unit/UnitPrefix.java b/src/org/unitConverter/unit/UnitPrefix.java
new file mode 100755
index 0000000..289e60f
--- /dev/null
+++ b/src/org/unitConverter/unit/UnitPrefix.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+/**
+ * A prefix that can be attached onto the front of any unit, which multiplies it by a certain value
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public interface UnitPrefix {
+ /**
+ * @return this prefix's multiplier
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ double getMultiplier();
+}
diff --git a/src/org/unitConverter/unit/UnitSystem.java b/src/org/unitConverter/unit/UnitSystem.java
new file mode 100755
index 0000000..550eff6
--- /dev/null
+++ b/src/org/unitConverter/unit/UnitSystem.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import java.util.Objects;
+
+import org.unitConverter.dimension.UnitDimension;
+
+/**
+ * A system of units. Each unit should be aware of its system.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+public interface UnitSystem {
+ /**
+ * Gets a base unit for this system and the provided dimension.
+ *
+ * @param dimension
+ * dimension used by base unit
+ * @return base unit
+ * @throws NullPointerException
+ * if dimension is null
+ * @since 2019-01-25
+ * @since v0.1.0
+ */
+ default BaseUnit getBaseUnit(final UnitDimension dimension) {
+ Objects.requireNonNull(dimension, "dimension must not be null.");
+ return new BaseUnit(dimension, this);
+ }
+
+ /**
+ * @return name of system
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ String getName();
+}
diff --git a/src/org/unitConverter/unit/UnitTest.java b/src/org/unitConverter/unit/UnitTest.java
new file mode 100755
index 0000000..931cc57
--- /dev/null
+++ b/src/org/unitConverter/unit/UnitTest.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.unitConverter.dimension.StandardDimensions;
+
+/**
+ * Testing the various Unit classes
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ */
+class UnitTest {
+ @Test
+ void testConversion() {
+ final BaseUnit metre = SI.METRE;
+ final Unit inch = metre.times(0.0254);
+
+ assertEquals(1.9, inch.convertToBase(75), 0.01);
+ }
+
+ @Test
+ void testEquals() {
+ final BaseUnit metre = SI.METRE;
+ final Unit meter = SI.SI.getBaseUnit(StandardDimensions.LENGTH);
+
+ assertEquals(metre, meter);
+ }
+}
diff --git a/src/org/unitConverter/unit/package-info.java b/src/org/unitConverter/unit/package-info.java
new file mode 100644
index 0000000..c4493ae
--- /dev/null
+++ b/src/org/unitConverter/unit/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * All of the classes that correspond to the units being converted.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-25
+ */
+package org.unitConverter.unit;
\ No newline at end of file
diff --git a/src/unitConverter/UnitsDatabase.java b/src/unitConverter/UnitsDatabase.java
deleted file mode 100755
index 9f5a6a2..0000000
--- a/src/unitConverter/UnitsDatabase.java
+++ /dev/null
@@ -1,641 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-import unitConverter.dimension.UnitDimension;
-import unitConverter.unit.AbstractUnit;
-import unitConverter.unit.BaseUnit;
-import unitConverter.unit.DefaultUnitPrefix;
-import unitConverter.unit.LinearUnit;
-import unitConverter.unit.SI;
-import unitConverter.unit.Unit;
-import unitConverter.unit.UnitPrefix;
-
-/**
- * A database of units and prefixes, and their names.
- *
- * @author Adrien Hopkins
- * @since 2019-01-07
- * @since v0.1.0
- */
-public final class UnitsDatabase {
- /**
- * The units in this system.
- *
- * @since 2019-01-07
- * @since v0.1.0
- */
- private final Map units;
-
- /**
- * The unit prefixes in this system.
- *
- * @since 2019-01-14
- * @since v0.1.0
- */
- private final Map prefixes;
-
- /**
- * The dimensions in this system.
- *
- * @since 2019-03-14
- */
- private final Map dimensions;
-
- /**
- * Creates the {@code UnitsDatabase}.
- *
- * @since 2019-01-10
- * @since v0.1.0
- */
- public UnitsDatabase() {
- this.units = new HashMap<>();
- this.prefixes = new HashMap<>();
- this.dimensions = new HashMap<>();
- }
-
- /**
- * Adds all units from a file, using data from the database to parse them.
- *
- * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
- * any number of tab characters.
- *
- *
- * Allowed exceptions:
- *
- *
Any line that begins with the '#' character is considered a comment and ignored.
- *
Blank lines are also ignored
- *
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.
- *
- *
- * @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()) {
- final String line = reader.readLine();
- lineCounter++;
-
- // ignore lines that start with a # sign - they're comments
- if (line.startsWith("#") || line.isEmpty()) {
- continue;
- }
-
- // 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;
- }
- AbstractUnit.incrementUnitCounter();
- if (unit instanceof BaseUnit) {
- AbstractUnit.incrementBaseUnitCounter();
- }
- this.addUnit(name, unit);
- }
- }
- }
- } 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
- * dimension's name
- * @param dimension
- * dimension to add
- * @throws NullPointerException
- * if name or dimension is null
- * @since 2019-03-14
- */
- public void addDimension(final String name, final UnitDimension dimension) {
- this.dimensions.put(Objects.requireNonNull(name, "name must not be null."),
- Objects.requireNonNull(dimension, "dimension must not be null."));
- }
-
- /**
- * 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
- * @since 2019-01-14
- * @since v0.1.0
- */
- public void addPrefix(final String name, final UnitPrefix prefix) {
- 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
- * @since 2019-01-10
- * @since v0.1.0
- */
- public void addUnit(final String name, final Unit unit) {
- this.units.put(Objects.requireNonNull(name, "name must not be null."),
- Objects.requireNonNull(unit, "unit must not be null."));
- }
-
- /**
- * Tests if the database has a unit dimension with this name.
- *
- * @param name
- * name to test
- * @return if database contains name
- * @since 2019-03-14
- */
- public boolean containsDimensionName(final String name) {
- return this.dimensions.containsKey(name);
- }
-
- /**
- * Tests if the database has a unit with this name, ignoring prefixes
- *
- * @param name
- * name to test
- * @return if database contains name
- * @since 2019-01-13
- * @since v0.1.0
- */
- public boolean containsPrefixlessUnitName(final String name) {
- return this.units.containsKey(name);
- }
-
- /**
- * Tests if the database has a unit prefix with this name.
- *
- * @param name
- * name to test
- * @return if database contains name
- * @since 2019-01-13
- * @since v0.1.0
- */
- 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
- *
- * @param name
- * name to test
- * @return if database contains name
- * @since 2019-01-13
- * @since v0.1.0
- */
- public boolean containsUnitName(final String name) {
- // check for prefixes
- for (final String prefixName : this.prefixNameSet()) {
- if (name.startsWith(prefixName))
- if (this.containsUnitName(name.substring(prefixName.length())))
- return true;
- }
- return this.units.containsKey(name);
- }
-
- /**
- * @return an immutable set of all of the dimension names in this database.
- * @since 2019-03-14
- */
- public Set dimensionNameSet() {
- return Collections.unmodifiableSet(this.dimensions.keySet());
- }
-
- /**
- * Gets a unit dimension from the database using its name.
- *
- * @param name
- * dimension's name
- * @return dimension
- * @since 2019-03-14
- */
- public UnitDimension getDimension(final String name) {
- return this.dimensions.get(name);
- }
-
- /**
- * Gets a unit prefix from the database from its name
- *
- * @param name
- * prefix's name
- * @return prefix
- * @since 2019-01-10
- * @since v0.1.0
- */
- public UnitPrefix getPrefix(final String name) {
- return this.prefixes.get(name);
- }
-
- /**
- * Gets a unit prefix from a prefix expression
- *
- * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
- * another prefix
- *
- *
- * @param expression
- * expression to input
- * @return prefix
- * @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.");
-
- try {
- return new DefaultUnitPrefix(Double.parseDouble(expression));
- } catch (final NumberFormatException e) {
- if (expression.contains("^")) {
- final String[] baseAndExponent = expression.split("\\^");
-
- final double base;
- try {
- base = Double.parseDouble(baseAndExponent[0]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Base of exponientation must be a number.");
- }
-
- final int exponent;
- try {
- exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponent must be an integer.");
- }
-
- return new DefaultUnitPrefix(Math.pow(base, exponent));
- } else {
- if (!this.containsPrefixName(expression))
- throw new IllegalArgumentException("Unrecognized prefix name \"" + expression + "\".");
- return this.getPrefix(expression);
- }
- }
- }
-
- /**
- * Gets a unit from the database from its name, ignoring prefixes.
- *
- * @param name
- * unit's name
- * @return unit
- * @since 2019-01-10
- * @since v0.1.0
- */
- public Unit getPrefixlessUnit(final String name) {
- return this.units.get(name);
- }
-
- /**
- * Gets a unit from the database from its name, looking for prefixes.
- *
- * @param name
- * unit's name
- * @return unit
- * @since 2019-01-10
- * @since v0.1.0
- */
- public Unit getUnit(final String name) {
- if (name.contains("^")) {
- final String[] baseAndExponent = name.split("\\^");
-
- LinearUnit base;
- try {
- base = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(baseAndExponent[0]));
- } catch (final NumberFormatException e2) {
- final Unit unit = this.getUnit(baseAndExponent[0]);
- if (unit instanceof LinearUnit) {
- base = (LinearUnit) unit;
- } else if (unit instanceof BaseUnit) {
- base = ((BaseUnit) unit).asLinearUnit();
- } else
- throw new IllegalArgumentException("Base of exponientation must be a linear or base unit.");
- }
-
- final int exponent;
- try {
- exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponent must be an integer.");
- }
-
- final LinearUnit exponentiated = base.toExponent(exponent);
- if (exponentiated.getConversionFactor() == 1)
- return exponentiated.getSystem().getBaseUnit(exponentiated.getDimension());
- else
- return exponentiated;
- } else {
- for (final String prefixName : this.prefixNameSet()) {
- // check for a prefix
- if (name.startsWith(prefixName)) {
- // prefix found! Make sure what comes after it is actually a unit!
- final String prefixless = name.substring(prefixName.length());
- if (this.containsUnitName(prefixless)) {
- // yep, it's a proper prefix! Get the unit!
- final Unit unit = this.getUnit(prefixless);
- final UnitPrefix prefix = this.getPrefix(prefixName);
-
- // Prefixes only work with linear and base units, so make sure it's one of those
- if (unit instanceof LinearUnit) {
- final LinearUnit linearUnit = (LinearUnit) unit;
- return linearUnit.times(prefix.getMultiplier());
- } else if (unit instanceof BaseUnit) {
- final BaseUnit baseUnit = (BaseUnit) unit;
- return baseUnit.times(prefix.getMultiplier());
- }
- }
- }
- }
- return this.units.get(name);
- }
- }
-
- /**
- * Uses the database's unit data to parse an expression into a unit
- *
- * The expression is a series of any of the following:
- *
- *
The name of a unit, which multiplies or divides the result based on preceding operators
- *
The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
- * other is equivalent to multiplication)
- *
The operator '^' which exponentiates. Exponents must be integers.
- *
A number which is multiplied or divided
- *
- * This method only works with linear units.
- *
- * @param expression
- * expression to parse
- * @throws IllegalArgumentException
- * if the expression cannot be parsed
- * @throws NullPointerException
- * if any argument is null
- * @since 2019-01-07
- * @since v0.1.0
- */
- public Unit getUnitFromExpression(final String expression) {
- Objects.requireNonNull(expression, "expression must not be null.");
-
- // parse the expression
- // start with an "empty" unit then apply operations on it
- LinearUnit unit = SI.SI.getBaseUnit(UnitDimension.EMPTY).asLinearUnit();
- boolean dividing = false;
-
- // if I'm just creating an alias, just create one instead of going through the parsing process
- if (!expression.contains(" ") && !expression.contains("*") && !expression.contains("/")
- && !expression.contains("(") && !expression.contains(")") && !expression.contains("^")) {
- try {
- return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(expression));
- } catch (final NumberFormatException e) {
- if (!this.containsUnitName(expression))
- throw new IllegalArgumentException("Unrecognized unit name \"" + expression + "\".");
- return this.getUnit(expression);
- }
- }
-
- // \\* means "asterisk", * is reserved
- for (final String part : expression.replaceAll("\\*", " \\* ").replaceAll("/", " / ").replaceAll(" \\^", "\\^")
- .replaceAll("\\^ ", "\\^").split(" ")) {
- if ("".equals(part)) {
- continue;
- }
- // "unit1 unit2" is the same as "unit1 * unit2", so multiplication signs do nothing
- if ("*".equals(part)) {
- continue;
- }
- // When I reach a division sign, don't parse a unit, instead tell myself I'm going to divide the next
- // thing
- if ("/".equals(part) || "per".equals(part)) {
- dividing = true;
- continue;
- }
-
- try {
- final double partAsNumber = Double.parseDouble(part); // if this works, it's a number
- // this code should not throw any exceptions, so I'm going to throw AssertionErrors if it does
- try {
- if (dividing) {
- unit = unit.dividedBy(partAsNumber);
- dividing = false;
- } else {
- unit = unit.times(partAsNumber);
- }
- } catch (final Exception e) {
- throw new AssertionError(e);
- }
- } catch (final NumberFormatException e) {
- // it's a unit, try that
-
- if (part.contains("(") && part.endsWith(")")) {
- // the unitsfile is looking for a nonlinear unit
- final String[] unitAndValue = part.split("\\(");
-
- // this will work because I've checked that it contains a (
- final String unitName = unitAndValue[0];
- final String valueStringWithBracket = unitAndValue[unitAndValue.length - 1];
- final String valueString = valueStringWithBracket.substring(0, valueStringWithBracket.length() - 1);
- final double value;
-
- // try to get the value - else throw an error
- try {
- value = Double.parseDouble(valueString);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Unparseable value " + valueString);
- }
-
- // get this unit in a linear form
- if (!this.containsPrefixlessUnitName(unitName))
- throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
- final Unit partUnit = this.getPrefixlessUnit(unitName);
- final LinearUnit multiplier = partUnit.getBase().times(partUnit.convertToBase(value));
-
- // finally, add it to the expression
- if (dividing) {
- unit = unit.dividedBy(multiplier);
- dividing = false;
- } else {
- unit = unit.times(multiplier);
- }
- } else {
- // check for exponientation
- if (part.contains("^")) {
- final String[] valueAndExponent = part.split("\\^");
- // this will always work because of the contains check
- final String valueString = valueAndExponent[0];
- final String exponentString = valueAndExponent[valueAndExponent.length - 1];
-
- LinearUnit value;
-
- // first, try to get the value
- try {
- final double valueAsNumber = Double.parseDouble(valueString);
-
- value = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(valueAsNumber);
- } catch (final NumberFormatException e2) {
-
- // look for a unit
- if (!this.containsUnitName(valueString))
- throw new IllegalArgumentException("Unrecognized unit name \"" + valueString + "\".");
- final Unit valueUnit = this.getUnit(valueString);
-
- // try to turn the value into a linear unit
- if (valueUnit instanceof LinearUnit) {
- value = (LinearUnit) valueUnit;
- } else if (valueUnit instanceof BaseUnit) {
- value = ((BaseUnit) valueUnit).asLinearUnit();
- } else
- throw new IllegalArgumentException("Only linear and base units can be exponientated.");
- }
-
- // now, try to get the exponent
- final int exponent;
- try {
- exponent = Integer.parseInt(exponentString);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponents must be integers.");
- }
-
- final LinearUnit exponientated = value.toExponent(exponent);
-
- if (dividing) {
- unit = unit.dividedBy(exponientated);
- dividing = false;
- } else {
- unit = unit.times(exponientated);
- }
- } else {
- // no exponent - look for a unit
- // the unitsfile is looking for a linear unit
- if (!this.containsUnitName(part))
- throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
- Unit other = this.getUnit(part);
- if (other instanceof BaseUnit) {
- other = ((BaseUnit) other).asLinearUnit();
- }
- if (other instanceof LinearUnit) {
- if (dividing) {
- unit = unit.dividedBy((LinearUnit) other);
- dividing = false;
- } else {
- unit = unit.times((LinearUnit) other);
- }
- } else
- throw new IllegalArgumentException(
- "Only linear or base units can be multiplied and divided. If you want to use a non-linear unit, try the format UNITNAME(VALUE).");
- }
- }
- }
- }
-
- // replace conversion-factor-1 linear units with base units
- // this improves the autogenerated names, allowing them to use derived SI names
- if (unit != null && unit.getConversionFactor() == 1)
- return unit.getSystem().getBaseUnit(unit.getDimension());
- else
- return unit;
- }
-
- /**
- * @return an immutable set of all of the unit names in this database, ignoring prefixes
- * @since 2019-01-14
- * @since v0.1.0
- */
- public Set prefixlessUnitNameSet() {
- return Collections.unmodifiableSet(this.units.keySet());
- }
-
- /**
- * @return an immutable set of all of the prefix names in this database
- * @since 2019-01-14
- * @since v0.1.0
- */
- public Set prefixNameSet() {
- return Collections.unmodifiableSet(this.prefixes.keySet());
- }
-}
diff --git a/src/unitConverter/converterGUI/DelegateListModel.java b/src/unitConverter/converterGUI/DelegateListModel.java
deleted file mode 100755
index 0e9b342..0000000
--- a/src/unitConverter/converterGUI/DelegateListModel.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.converterGUI;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
-import javax.swing.AbstractListModel;
-
-/**
- * A list model that delegates to a list.
- *
- * It is recommended to use the delegate methods in DelegateListModel instead of the delegated list's methods because
- * the delegate methods handle updating the list.
- *
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-final class DelegateListModel extends AbstractListModel implements List {
- /**
- * @since 2019-01-14
- * @since v0.1.0
- */
- private static final long serialVersionUID = 8985494428224810045L;
-
- /**
- * The list that this model is a delegate to.
- *
- * @since 2019-01-14
- * @since v0.1.0
- */
- private final List delegate;
-
- /**
- * Creates the {@code DelegateListModel}.
- *
- * @param delegate
- * list to delegate
- * @since 2019-01-14
- * @since v0.1.0
- */
- public DelegateListModel(final List delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public boolean add(final E element) {
- final int index = this.delegate.size();
- final boolean success = this.delegate.add(element);
- this.fireIntervalAdded(this, index, index);
- return success;
- }
-
- @Override
- public void add(final int index, final E element) {
- this.delegate.add(index, element);
- this.fireIntervalAdded(this, index, index);
- }
-
- @Override
- public boolean addAll(final Collection extends E> c) {
- boolean changed = false;
- for (final E e : c) {
- if (this.add(e)) {
- changed = true;
- }
- }
- return changed;
- }
-
- @Override
- public boolean addAll(final int index, final Collection extends E> c) {
- for (final E e : c) {
- this.add(index, e);
- }
- return !c.isEmpty(); // Since this is a list, it will always change if c has elements.
- }
-
- @Override
- public void clear() {
- final int oldSize = this.delegate.size();
- this.delegate.clear();
- if (oldSize >= 1) {
- this.fireIntervalRemoved(this, 0, oldSize - 1);
- }
- }
-
- @Override
- public boolean contains(final Object elem) {
- return this.delegate.contains(elem);
- }
-
- @Override
- public boolean containsAll(final Collection> c) {
- for (final Object e : c) {
- if (!c.contains(e))
- return false;
- }
- return true;
- }
-
- @Override
- public E get(final int index) {
- return this.delegate.get(index);
- }
-
- @Override
- public E getElementAt(final int index) {
- return this.delegate.get(index);
- }
-
- @Override
- public int getSize() {
- return this.delegate.size();
- }
-
- @Override
- public int indexOf(final Object elem) {
- return this.delegate.indexOf(elem);
- }
-
- @Override
- public boolean isEmpty() {
- return this.delegate.isEmpty();
- }
-
- @Override
- public Iterator iterator() {
- return this.delegate.iterator();
- }
-
- @Override
- public int lastIndexOf(final Object elem) {
- return this.delegate.lastIndexOf(elem);
- }
-
- @Override
- public ListIterator listIterator() {
- return this.delegate.listIterator();
- }
-
- @Override
- public ListIterator listIterator(final int index) {
- return this.delegate.listIterator(index);
- }
-
- @Override
- public E remove(final int index) {
- final E returnValue = this.delegate.get(index);
- this.delegate.remove(index);
- this.fireIntervalRemoved(this, index, index);
- return returnValue;
- }
-
- @Override
- public boolean remove(final Object o) {
- final int index = this.delegate.indexOf(o);
- final boolean returnValue = this.delegate.remove(o);
- this.fireIntervalRemoved(this, index, index);
- return returnValue;
- }
-
- @Override
- public boolean removeAll(final Collection> c) {
- boolean changed = false;
- for (final Object e : c) {
- if (this.remove(e)) {
- changed = true;
- }
- }
- return changed;
- }
-
- @Override
- public boolean retainAll(final Collection> c) {
- final int oldSize = this.size();
- final boolean returnValue = this.delegate.retainAll(c);
- this.fireIntervalRemoved(this, this.size(), oldSize - 1);
- return returnValue;
- }
-
- @Override
- public E set(final int index, final E element) {
- final E returnValue = this.delegate.get(index);
- this.delegate.set(index, element);
- this.fireContentsChanged(this, index, index);
- return returnValue;
- }
-
- @Override
- public int size() {
- return this.delegate.size();
- }
-
- @Override
- public List subList(final int fromIndex, final int toIndex) {
- return this.delegate.subList(fromIndex, toIndex);
- }
-
- @Override
- public Object[] toArray() {
- return this.delegate.toArray();
- }
-
- @Override
- public T[] toArray(final T[] a) {
- return this.delegate.toArray(a);
- }
-
- @Override
- public String toString() {
- return this.delegate.toString();
- }
-}
diff --git a/src/unitConverter/converterGUI/FilterComparator.java b/src/unitConverter/converterGUI/FilterComparator.java
deleted file mode 100755
index 27ec3ab..0000000
--- a/src/unitConverter/converterGUI/FilterComparator.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.converterGUI;
-
-import java.util.Comparator;
-import java.util.Objects;
-
-/**
- * A comparator that compares strings using a filter.
- *
- * @author Adrien Hopkins
- * @since 2019-01-15
- * @since v0.1.0
- */
-public final class FilterComparator implements Comparator {
- /**
- * The filter that the comparator is filtered by.
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- private final String filter;
- /**
- * The comparator to use if the arguments are otherwise equal.
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- private final Comparator comparator;
-
- /**
- * Creates the {@code FilterComparator}.
- *
- * @param filter
- * @since 2019-01-15
- * @since v0.1.0
- */
- public FilterComparator(final String filter) {
- this(filter, null);
- }
-
- /**
- * Creates the {@code FilterComparator}.
- *
- * @param filter
- * string to filter by
- * @param comparator
- * comparator to fall back to if all else fails, null is compareTo.
- * @since 2019-01-15
- * @since v0.1.0
- * @throws NullPointerException
- * if filter is null
- */
- public FilterComparator(final String filter, final Comparator comparator) {
- this.filter = Objects.requireNonNull(filter, "filter must not be null.");
- this.comparator = comparator;
- }
-
- @Override
- public int compare(final String arg0, final String arg1) {
- // elements that start with the filter always go first
- if (arg0.startsWith(this.filter) && !arg1.startsWith(this.filter))
- return -1;
- else if (!arg0.startsWith(this.filter) && arg1.startsWith(this.filter))
- return 1;
-
- // elements that contain the filter but don't start with them go next
- if (arg0.contains(this.filter) && !arg1.contains(this.filter))
- return -1;
- else if (!arg0.contains(this.filter) && !arg1.contains(this.filter))
- return 1;
-
- // other elements go last
- if (this.comparator == null)
- return arg0.compareTo(arg1);
- else
- return this.comparator.compare(arg0, arg1);
- }
-}
diff --git a/src/unitConverter/converterGUI/GridBagBuilder.java b/src/unitConverter/converterGUI/GridBagBuilder.java
deleted file mode 100755
index e036677..0000000
--- a/src/unitConverter/converterGUI/GridBagBuilder.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.converterGUI;
-
-import java.awt.GridBagConstraints;
-import java.awt.Insets;
-
-/**
- * A builder for Java's {@link java.awt.GridBagConstraints} class.
- *
- * @author Adrien Hopkins
- * @since 2018-11-30
- * @since v0.1.0
- */
-final class GridBagBuilder {
- /**
- * The built {@code GridBagConstraints}'s {@code gridx} property.
- *
- * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has
- * gridx=0. The leading edge of a component's display area is its left edge for a horizontal,
- * left-to-right container and its right edge for a horizontal, right-to-left container. The value
- * RELATIVE specifies that the component be placed immediately following the component that was added
- * to the container just before this component was added.
- *
- * The default value is RELATIVE. gridx should be a non-negative value.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#gridy
- * @see java.awt.ComponentOrientation
- */
- private final int gridx;
-
- /**
- * The built {@code GridBagConstraints}'s {@code gridy} property.
- *
- * Specifies the cell at the top of the component's display area, where the topmost cell has gridy=0.
- * The value RELATIVE specifies that the component be placed just below the component that was added to
- * the container just before this component was added.
- *
- * The default value is RELATIVE. gridy should be a non-negative value.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#gridx
- */
- private final int gridy;
-
- /**
- * The built {@code GridBagConstraints}'s {@code gridwidth} property.
- *
- * Specifies the number of cells in a row for the component's display area.
- *
- * Use REMAINDER to specify that the component's display area will be from gridx to the
- * last cell in the row. Use RELATIVE to specify that the component's display area will be from
- * gridx to the next to the last one in its row.
- *
- * gridwidth should be non-negative and the default value is 1.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#gridheight
- */
- private final int gridwidth;
-
- /**
- * The built {@code GridBagConstraints}'s {@code gridheight} property.
- *
- * Specifies the number of cells in a column for the component's display area.
- *
- * Use REMAINDER to specify that the component's display area will be from gridy to the
- * last cell in the column. Use RELATIVE to specify that the component's display area will be from
- * gridy to the next to the last one in its column.
- *
- * gridheight should be a non-negative value and the default value is 1.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#gridwidth
- */
- private final int gridheight;
-
- /**
- * The built {@code GridBagConstraints}'s {@code weightx} property.
- *
- * Specifies how to distribute extra horizontal space.
- *
- * The grid bag layout manager calculates the weight of a column to be the maximum weightx of all the
- * components in a column. If the resulting layout is smaller horizontally than the area it needs to fill, the extra
- * space is distributed to each column in proportion to its weight. A column that has a weight of zero receives no
- * extra space.
- *
- * If all the weights are zero, all the extra space appears between the grids of the cell and the left and right
- * edges.
- *
- * The default value of this field is 0. weightx should be a non-negative value.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#weighty
- */
- private double weightx;
-
- /**
- * The built {@code GridBagConstraints}'s {@code weighty} property.
- *
- * Specifies how to distribute extra vertical space.
- *
- * The grid bag layout manager calculates the weight of a row to be the maximum weighty of all the
- * components in a row. If the resulting layout is smaller vertically than the area it needs to fill, the extra
- * space is distributed to each row in proportion to its weight. A row that has a weight of zero receives no extra
- * space.
- *
- * If all the weights are zero, all the extra space appears between the grids of the cell and the top and bottom
- * edges.
- *
- * The default value of this field is 0. weighty should be a non-negative value.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#weightx
- */
- private double weighty;
-
- /**
- * The built {@code GridBagConstraints}'s {@code anchor} property.
- *
- * This field is used when the component is smaller than its display area. It determines where, within the display
- * area, to place the component.
- *
- * There are three kinds of possible values: orientation relative, baseline relative and absolute. Orientation
- * relative values are interpreted relative to the container's component orientation property, baseline relative
- * values are interpreted relative to the baseline and absolute values are not. The absolute values are:
- * CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST,
- * SOUTH, SOUTHWEST, WEST, and NORTHWEST. The orientation
- * relative values are: PAGE_START, PAGE_END, LINE_START,
- * LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_START
- * and LAST_LINE_END. The baseline relative values are: BASELINE,
- * BASELINE_LEADING, BASELINE_TRAILING, ABOVE_BASELINE,
- * ABOVE_BASELINE_LEADING, ABOVE_BASELINE_TRAILING, BELOW_BASELINE,
- * BELOW_BASELINE_LEADING, and BELOW_BASELINE_TRAILING. The default value is
- * CENTER.
- *
- * @serial
- * @see #clone()
- * @see java.awt.ComponentOrientation
- */
- private int anchor;
-
- /**
- * The built {@code GridBagConstraints}'s {@code fill} property.
- *
- * This field is used when the component's display area is larger than the component's requested size. It determines
- * whether to resize the component, and if so, how.
- *
- * The following values are valid for fill:
- *
- *
- *
NONE: Do not resize the component.
- *
HORIZONTAL: Make the component wide enough to fill its display area horizontally, but do not
- * change its height.
- *
VERTICAL: Make the component tall enough to fill its display area vertically, but do not change
- * its width.
- *
BOTH: Make the component fill its display area entirely.
- *
- *
- * The default value is NONE.
- *
- * @serial
- * @see #clone()
- */
- private int fill;
-
- /**
- * The built {@code GridBagConstraints}'s {@code insets} property.
- *
- * This field specifies the external padding of the component, the minimum amount of space between the component and
- * the edges of its display area.
- *
- * The default value is new Insets(0, 0, 0, 0).
- *
- * @serial
- * @see #clone()
- */
- private Insets insets;
-
- /**
- * The built {@code GridBagConstraints}'s {@code ipadx} property.
- *
- * This field specifies the internal padding of the component, how much space to add to the minimum width of the
- * component. The width of the component is at least its minimum width plus ipadx pixels.
- *
- * The default value is 0.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#ipady
- */
- private int ipadx;
-
- /**
- * The built {@code GridBagConstraints}'s {@code ipady} property.
- *
- * This field specifies the internal padding, that is, how much space to add to the minimum height of the component.
- * The height of the component is at least its minimum height plus ipady pixels.
- *
- * The default value is 0.
- *
- * @serial
- * @see #clone()
- * @see java.awt.GridBagConstraints#ipadx
- */
- private int ipady;
-
- /**
- * @param gridx
- * x position
- * @param gridy
- * y position
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder(final int gridx, final int gridy) {
- this(gridx, gridy, 1, 1);
- }
-
- /**
- * @param gridx
- * x position
- * @param gridy
- * y position
- * @param gridwidth
- * number of cells occupied horizontally
- * @param gridheight
- * number of cells occupied vertically
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight) {
- this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE,
- new Insets(0, 0, 0, 0), 0, 0);
- }
-
- /**
- * @param gridx
- * x position
- * @param gridy
- * y position
- * @param gridwidth
- * number of cells occupied horizontally
- * @param gridheight
- * number of cells occupied vertically
- * @param weightx
- * @param weighty
- * @param anchor
- * @param fill
- * @param insets
- * @param ipadx
- * @param ipady
- * @since 2018-11-30
- * @since v0.1.0
- */
- private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight,
- final double weightx, final double weighty, final int anchor, final int fill, final Insets insets,
- final int ipadx, final int ipady) {
- super();
- this.gridx = gridx;
- this.gridy = gridy;
- this.gridwidth = gridwidth;
- this.gridheight = gridheight;
- this.weightx = weightx;
- this.weighty = weighty;
- this.anchor = anchor;
- this.fill = fill;
- this.insets = (Insets) insets.clone();
- this.ipadx = ipadx;
- this.ipady = ipady;
- }
-
- /**
- * @return {@code GridBagConstraints} created by this builder
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagConstraints build() {
- return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, this.gridheight, this.weightx,
- this.weighty, this.anchor, this.fill, this.insets, this.ipadx, this.ipady);
- }
-
- /**
- * @return anchor
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getAnchor() {
- return this.anchor;
- }
-
- /**
- * @return fill
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getFill() {
- return this.fill;
- }
-
- /**
- * @return gridheight
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getGridheight() {
- return this.gridheight;
- }
-
- /**
- * @return gridwidth
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getGridwidth() {
- return this.gridwidth;
- }
-
- /**
- * @return gridx
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getGridx() {
- return this.gridx;
- }
-
- /**
- * @return gridy
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getGridy() {
- return this.gridy;
- }
-
- /**
- * @return insets
- * @since 2018-11-30
- * @since v0.1.0
- */
- public Insets getInsets() {
- return this.insets;
- }
-
- /**
- * @return ipadx
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getIpadx() {
- return this.ipadx;
- }
-
- /**
- * @return ipady
- * @since 2018-11-30
- * @since v0.1.0
- */
- public int getIpady() {
- return this.ipady;
- }
-
- /**
- * @return weightx
- * @since 2018-11-30
- * @since v0.1.0
- */
- public double getWeightx() {
- return this.weightx;
- }
-
- /**
- * @return weighty
- * @since 2018-11-30
- * @since v0.1.0
- */
- public double getWeighty() {
- return this.weighty;
- }
-
- /**
- * @param anchor
- * anchor to set
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder setAnchor(final int anchor) {
- this.anchor = anchor;
- return this;
- }
-
- /**
- * @param fill
- * fill to set
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder setFill(final int fill) {
- this.fill = fill;
- return this;
- }
-
- /**
- * @param insets
- * insets to set
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder setInsets(final Insets insets) {
- this.insets = insets;
- return this;
- }
-
- /**
- * @param ipadx
- * ipadx to set
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder setIpadx(final int ipadx) {
- this.ipadx = ipadx;
- return this;
- }
-
- /**
- * @param ipady
- * ipady to set
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder setIpady(final int ipady) {
- this.ipady = ipady;
- return this;
- }
-
- /**
- * @param weightx
- * weightx to set
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder setWeightx(final double weightx) {
- this.weightx = weightx;
- return this;
- }
-
- /**
- * @param weighty
- * weighty to set
- * @since 2018-11-30
- * @since v0.1.0
- */
- public GridBagBuilder setWeighty(final double weighty) {
- this.weighty = weighty;
- return this;
- }
-}
diff --git a/src/unitConverter/converterGUI/UnitConverterGUI.java b/src/unitConverter/converterGUI/UnitConverterGUI.java
deleted file mode 100755
index cf78ea8..0000000
--- a/src/unitConverter/converterGUI/UnitConverterGUI.java
+++ /dev/null
@@ -1,777 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.converterGUI;
-
-import java.awt.BorderLayout;
-import java.awt.GridLayout;
-import java.io.File;
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.function.Predicate;
-
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JFormattedTextField;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JSlider;
-import javax.swing.JTabbedPane;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.ListModel;
-import javax.swing.ListSelectionModel;
-
-import unitConverter.UnitsDatabase;
-import unitConverter.dimension.StandardDimensions;
-import unitConverter.dimension.UnitDimension;
-import unitConverter.unit.AbstractUnit;
-import unitConverter.unit.NonlinearUnits;
-import unitConverter.unit.SI;
-import unitConverter.unit.Unit;
-import unitConverter.unit.UnitPrefix;
-
-/**
- * @author Adrien Hopkins
- * @since 2018-12-27
- * @since v0.1.0
- */
-final class UnitConverterGUI {
- private static class Presenter {
- /** The presenter's associated view. */
- private final View view;
-
- /** The units known by the program. */
- private final UnitsDatabase units;
-
- /** The names of all of the units */
- private final List unitNames;
-
- /** The names of all of the units, but filtered */
- private final DelegateListModel unitNamesFiltered;
-
- /** The names of all of the prefixes */
- private final List prefixNames;
-
- /** The names of all of the prefixes */
- private final DelegateListModel prefixNamesFiltered;
-
- private final Comparator prefixNameComparator;
-
- private int significantFigures = 6;
-
- /**
- * Creates the presenter.
- *
- * @param view
- * presenter's associated view
- * @since 2018-12-27
- * @since v0.1.0
- */
- Presenter(final View view) {
- this.view = view;
-
- // load initial units
- this.units = new UnitsDatabase();
- this.units.addUnit("metre", SI.METRE);
- this.units.addUnit("kilogram", SI.KILOGRAM);
- this.units.addUnit("gram", SI.KILOGRAM.dividedBy(1000));
- this.units.addUnit("second", SI.SECOND);
- this.units.addUnit("ampere", SI.AMPERE);
- this.units.addUnit("kelvin", SI.KELVIN);
- this.units.addUnit("mole", SI.MOLE);
- this.units.addUnit("candela", SI.CANDELA);
- this.units.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION));
- this.units.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY));
- // nonlinear units - must be loaded manually
- this.units.addUnit("tempCelsius", NonlinearUnits.CELSIUS);
- this.units.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
-
- this.units.addAllFromFile(new File("unitsfile.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
- this.prefixNameComparator = (o1, o2) -> {
- if (!Presenter.this.units.containsPrefixName(o1))
- return -1;
- else if (!Presenter.this.units.containsPrefixName(o2))
- return 1;
-
- final UnitPrefix p1 = Presenter.this.units.getPrefix(o1);
- final UnitPrefix p2 = Presenter.this.units.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.units.prefixlessUnitNameSet());
- this.unitNames.sort(null); // sorts it using Comparable
-
- this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixlessUnitNameSet()));
- this.unitNamesFiltered.sort(null); // sorts it using Comparable
-
- this.prefixNames = new ArrayList<>(this.units.prefixNameSet());
- this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator
-
- this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet()));
- this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator
-
- System.out.printf("Successfully loaded %d units (%d base units)", AbstractUnit.getUnitCount(),
- AbstractUnit.getBaseUnitCount());
- }
-
- /**
- * Runs whenever the convert button is pressed.
- *
- *
- * 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.
- *
- *
- * @since 2019-01-26
- * @since v0.1.0
- */
- public final void convert() {
- final String fromUnitString = this.view.getFromText();
- final String toUnitString = this.view.getToText();
-
- // try to parse from
- final Unit from;
- try {
- from = this.units.getUnitFromExpression(fromUnitString);
- } catch (final IllegalArgumentException e) {
- 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 {
- to = this.units.getUnitFromExpression(toUnitString);
- } catch (final IllegalArgumentException e) {
- this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage());
- return;
- }
-
- // if I can't convert, leave
- if (!from.canConvertTo(to)) {
- this.view.showErrorDialog("Conversion Error",
- String.format("Cannot convert between %s and %s", fromUnitString, toUnitString));
- return;
- }
-
- value = to.convertFromBase(from.convertToBase(1));
-
- // round value
- final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures));
- String output = bigValue.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);
- }
- }
-
- this.view.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString));
- }
-
- /**
- * Filters the filtered model for units
- *
- * @param filter
- * filter to use
- * @since 2019-01-15
- * @since v0.1.0
- */
- private final void filterFilteredPrefixModel(final Predicate filter) {
- this.prefixNamesFiltered.clear();
- for (final String prefixName : this.prefixNames) {
- if (filter.test(prefixName)) {
- this.prefixNamesFiltered.add(prefixName);
- }
- }
- }
-
- /**
- * Filters the filtered model for units
- *
- * @param filter
- * filter to use
- * @since 2019-01-15
- * @since v0.1.0
- */
- private final void filterFilteredUnitModel(final Predicate filter) {
- this.unitNamesFiltered.clear();
- for (final String unitName : this.unitNames) {
- if (filter.test(unitName)) {
- this.unitNamesFiltered.add(unitName);
- }
- }
- }
-
- /**
- * @return a list model of all of the unit keys
- * @since 2019-01-14
- * @since v0.1.0
- */
- public final ListModel keyListModel() {
- return this.unitNamesFiltered;
- }
-
- /**
- * Runs whenever the prefix filter is changed.
- *
- * Filters the prefix list then sorts it using a {@code FilterComparator}.
- *
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public final void prefixFilterUpdated() {
- final String filter = this.view.getPrefixFilterText();
- if (filter.equals("")) {
- this.filterFilteredPrefixModel(t -> true);
- } else {
- this.filterFilteredPrefixModel(t -> t.contains(filter));
- }
- this.prefixNamesFiltered.sort(new FilterComparator(filter));
- }
-
- /**
- * @return a list model of all of the prefix names
- * @since 2019-01-15
- */
- public final ListModel prefixNameListModel() {
- return this.prefixNamesFiltered;
- }
-
- /**
- * Runs whenever a prefix is selected in the viewer.
- *
- * Shows its information in the text box to the right.
- *
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public final void prefixSelected() {
- final int index = this.view.getPrefixListSelection();
- if (index == -1)
- return;
- else {
- final String prefixName = this.prefixNamesFiltered.get(index);
- final UnitPrefix prefix = this.units.getPrefix(prefixName);
-
- this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier()));
- }
- }
-
- /**
- * @param significantFigures
- * new value of significantFigures
- * @since 2019-01-15
- */
- public final void setSignificantFigures(final int significantFigures) {
- this.significantFigures = significantFigures;
- }
-
- /**
- * Runs whenever the unit filter is changed.
- *
- * Filters the unit list then sorts it using a {@code FilterComparator}.
- *
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public final void unitFilterUpdated() {
- final String filter = this.view.getUnitFilterText();
- if (filter.equals("")) {
- this.filterFilteredUnitModel(t -> true);
- } else {
- this.filterFilteredUnitModel(t -> t.contains(filter));
- }
- this.unitNamesFiltered.sort(new FilterComparator(filter));
- }
-
- /**
- * Runs whenever a unit is selected in the viewer.
- *
- * Shows its information in the text box to the right.
- *
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public void unitNameSelected() {
- final int index = this.view.getUnitListSelection();
- if (index == -1)
- return;
- else {
- final String unitName = this.unitNamesFiltered.get(index);
- final Unit unit = this.units.getUnit(unitName);
-
- this.view.setUnitTextBoxText(unit.toString());
- }
- }
- }
-
- private static class View {
- /** The view's frame. */
- private final JFrame frame;
- /** The view's associated presenter. */
- private final Presenter presenter;
-
- /** The list of unit names in the unit viewer */
- private final JList unitNameList;
- /** The list of prefix names in the prefix viewer */
- private final JList prefixNameList;
- /** The unit search box in the unit viewer */
- private final JTextField unitFilterEntry;
- /** The text box for unit data in the unit viewer */
- private final JTextArea unitTextBox;
- /** The prefix search box in the prefix viewer */
- private final JTextField prefixFilterEntry;
- /** The text box for prefix data in the prefix viewer */
- private final JTextArea prefixTextBox;
- /** The "From" entry in the conversion panel */
- private final JTextField fromEntry;
- /** The "To" entry in the conversion panel */
- private final JTextField toEntry;
- /** The output area in the conversion panel */
- private final JTextArea output;
-
- /**
- * Creates the {@code View}.
- *
- * @since 2019-01-14
- * @since v0.1.0
- */
- public View() {
- this.presenter = new Presenter(this);
- this.frame = new JFrame("Unit Converter");
- this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-
- // create the components
- this.unitNameList = new JList<>(this.presenter.keyListModel());
- this.prefixNameList = new JList<>(this.presenter.prefixNameListModel());
- this.unitFilterEntry = new JTextField();
- this.unitTextBox = new JTextArea();
- this.prefixFilterEntry = new JTextField();
- this.prefixTextBox = new JTextArea();
- this.fromEntry = new JTextField();
- this.toEntry = new JTextField();
- this.output = new JTextArea(2, 32);
-
- // create more components
- this.initComponents();
-
- this.frame.pack();
- }
-
- /**
- * @return text in "From" box in converter panel
- * @since 2019-01-15
- * @since v0.1.0
- */
- public String getFromText() {
- return this.fromEntry.getText();
- }
-
- /**
- * @return text in prefix filter
- * @since 2019-01-15
- * @since v0.1.0
- */
- public String getPrefixFilterText() {
- return this.prefixFilterEntry.getText();
- }
-
- /**
- * @return index of selected prefix
- * @since 2019-01-15
- * @since v0.1.0
- */
- public int getPrefixListSelection() {
- return this.prefixNameList.getSelectedIndex();
- }
-
- /**
- * @return text in "To" box in converter panel
- * @since 2019-01-26
- * @since v0.1.0
- */
- public String getToText() {
- return this.toEntry.getText();
- }
-
- /**
- * @return text in unit filter
- * @see javax.swing.text.JTextComponent#getText()
- */
- public String getUnitFilterText() {
- return this.unitFilterEntry.getText();
- }
-
- /**
- * @return index of selected unit
- * @since 2019-01-15
- * @since v0.1.0
- */
- public int getUnitListSelection() {
- return this.unitNameList.getSelectedIndex();
- }
-
- /**
- * Starts up the application.
- *
- * @since 2018-12-27
- * @since v0.1.0
- */
- public final void init() {
- this.frame.setVisible(true);
- }
-
- /**
- * Initializes the view's components.
- *
- * @since 2018-12-27
- * @since v0.1.0
- */
- private final void initComponents() {
- final JPanel masterPanel = new JPanel();
- this.frame.add(masterPanel);
-
- masterPanel.setLayout(new BorderLayout());
-
- { // pane with all of the tabs
- final JTabbedPane masterPane = new JTabbedPane();
- masterPanel.add(masterPane, BorderLayout.CENTER);
-
- { // a panel for unit conversion using a selector
- final JPanel convertUnitPanel = new JPanel();
- masterPane.addTab("Convert Units", convertUnitPanel);
-
- convertUnitPanel.setLayout(new BorderLayout());
-
- { // panel for input part
- final JPanel inputPanel = new JPanel();
- convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
-
- inputPanel.setLayout(new GridLayout(1, 3));
-
- { // panel for From things
- final JPanel fromPanel = new JPanel();
- inputPanel.add(fromPanel);
-
- fromPanel.setLayout(new BorderLayout());
-
- { // search box for from
- final JTextField fromSearch = new JTextField("Search...");
- fromPanel.add(fromSearch, BorderLayout.PAGE_START);
- }
-
- { // list for From units
- final JList fromList = new JList<>();
- fromPanel.add(fromList, BorderLayout.CENTER);
- }
- }
-
- { // for dimension selector and arrow that represents conversion
- final JPanel inBetweenPanel = new JPanel();
- inputPanel.add(inBetweenPanel);
-
- inBetweenPanel.setLayout(new BorderLayout());
-
- { // dimension selector
- final JComboBox dimensionSelector = new JComboBox<>(
- new String[] {"Select dimension..."});
- 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
- final JPanel toPanel = new JPanel();
- inputPanel.add(toPanel);
-
- toPanel.setLayout(new BorderLayout());
-
- { // search box for to
- final JTextField toSearch = new JTextField("Search...");
- toPanel.add(toSearch, BorderLayout.PAGE_START);
- }
-
- { // list for To units
- final JList toList = new JList<>();
- toPanel.add(toList, BorderLayout.CENTER);
- }
- }
-
- }
-
- { // 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);
- }
-
- { // value to convert
- final JTextField valueInput = new JFormattedTextField(
- new DecimalFormat("###############0.################"));
- valueInputPanel.add(valueInput, BorderLayout.CENTER);
- }
- }
-
- { // button to convert
- final JButton convertButton = new JButton("Convert");
- outputPanel.add(convertButton);
- }
-
- { // output of conversion
- final JLabel outputLabel = new JLabel();
- outputPanel.add(outputLabel);
- }
- }
- }
-
- { // panel for unit conversion using expressions
- final JPanel convertExpressionPanel = new JPanel();
- masterPane.addTab("Convert Unit Expressions", convertExpressionPanel);
-
- convertExpressionPanel.setLayout(new GridLayout(5, 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.convert());
- }
-
- { // output of conversion
- final JPanel outputPanel = new JPanel();
- convertExpressionPanel.add(outputPanel);
-
- outputPanel.setBorder(BorderFactory.createTitledBorder("Output"));
- outputPanel.setLayout(new GridLayout(1, 1));
-
- { // output
- outputPanel.add(this.output);
- this.output.setEditable(false);
- }
- }
-
- { // panel for specifying precision
- final JPanel sigDigPanel = new JPanel();
- convertExpressionPanel.add(sigDigPanel);
-
- sigDigPanel.setBorder(BorderFactory.createTitledBorder("Significant Digits"));
-
- { // slider
- final JSlider sigDigSlider = new JSlider(0, 12);
- sigDigPanel.add(sigDigSlider);
-
- sigDigSlider.setMajorTickSpacing(4);
- sigDigSlider.setMinorTickSpacing(1);
- sigDigSlider.setSnapToTicks(true);
- sigDigSlider.setPaintTicks(true);
- sigDigSlider.setPaintLabels(true);
-
- sigDigSlider.addChangeListener(
- e -> this.presenter.setSignificantFigures(sigDigSlider.getValue()));
- }
- }
- }
-
- { // panel to look up units
- final JPanel unitLookupPanel = new JPanel();
- masterPane.addTab("Unit Viewer", unitLookupPanel);
-
- unitLookupPanel.setLayout(new GridLayout());
-
- { // panel for listing and searching
- final JPanel listPanel = new JPanel();
- unitLookupPanel.add(listPanel);
-
- listPanel.setLayout(new BorderLayout());
-
- { // unit search box
- listPanel.add(this.unitFilterEntry, BorderLayout.PAGE_START);
- this.unitFilterEntry.addCaretListener(e -> this.presenter.unitFilterUpdated());
- }
-
- { // a list of units
- listPanel.add(new JScrollPane(this.unitNameList), BorderLayout.CENTER);
- this.unitNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
- this.unitNameList.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();
- masterPane.addTab("Prefix Viewer", prefixLookupPanel);
-
- prefixLookupPanel.setLayout(new GridLayout(1, 2));
-
- { // panel for listing and seaching
- final JPanel prefixListPanel = new JPanel();
- prefixLookupPanel.add(prefixListPanel);
-
- prefixListPanel.setLayout(new BorderLayout());
-
- { // prefix search box
- prefixListPanel.add(this.prefixFilterEntry, BorderLayout.PAGE_START);
- this.prefixFilterEntry.addCaretListener(e -> this.presenter.prefixFilterUpdated());
- }
-
- { // a list of prefixes
- prefixListPanel.add(new JScrollPane(this.prefixNameList), BorderLayout.CENTER);
- this.prefixNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
- this.prefixNameList.addListSelectionListener(e -> {
- this.presenter.prefixSelected();
- });
- }
- }
-
- { // the text box for prefix's toString
- prefixLookupPanel.add(this.prefixTextBox);
- this.unitTextBox.setEditable(false);
- }
- }
- }
- }
-
- /**
- * Sets the text in the output of the conversion panel.
- *
- * @param text
- * text to set
- * @since 2019-01-15
- * @since v0.1.0
- */
- public void setOutputText(final String text) {
- this.output.setText(text);
- }
-
- /**
- * Sets the text of the prefix text box.
- *
- * @param text
- * text to set
- * @since 2019-01-15
- * @since v0.1.0
- */
- public void setPrefixTextBoxText(final String text) {
- this.prefixTextBox.setText(text);
- }
-
- /**
- * Sets the text of the unit text box.
- *
- * @param t
- * text to set
- * @see javax.swing.text.JTextComponent#setText(java.lang.String)
- */
- public void setUnitTextBoxText(final String t) {
- this.unitTextBox.setText(t);
- }
-
- /**
- * Shows an error dialog.
- *
- * @param title
- * title of dialog
- * @param message
- * message in dialog
- * @since 2019-01-14
- * @since v0.1.0
- */
- public void showErrorDialog(final String title, final String message) {
- JOptionPane.showMessageDialog(this.frame, message, title, JOptionPane.ERROR_MESSAGE);
- }
- }
-
- public static void main(final String[] args) {
- new View().init();
- }
-}
diff --git a/src/unitConverter/converterGUI/package-info.java b/src/unitConverter/converterGUI/package-info.java
deleted file mode 100644
index 9f7fa57..0000000
--- a/src/unitConverter/converterGUI/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-/**
- * All classes that work to convert units.
- *
- * @author Adrien Hopkins
- * @since 2019-01-25
- */
-package unitConverter.converterGUI;
\ No newline at end of file
diff --git a/src/unitConverter/dimension/BaseDimension.java b/src/unitConverter/dimension/BaseDimension.java
deleted file mode 100755
index 0c09dce..0000000
--- a/src/unitConverter/dimension/BaseDimension.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.dimension;
-
-/**
- * A base dimension that makes up {@code UnitDimension} objects.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-public interface BaseDimension {
- /**
- * @return the dimension's name
- * @since 2018-12-22
- * @since v0.1.0
- */
- String getName();
-
- /**
- * @return a short string (usually one character) that represents this base dimension
- * @since 2018-12-22
- * @since v0.1.0
- */
- String getSymbol();
-}
diff --git a/src/unitConverter/dimension/OtherBaseDimension.java b/src/unitConverter/dimension/OtherBaseDimension.java
deleted file mode 100755
index 8c6d25d..0000000
--- a/src/unitConverter/dimension/OtherBaseDimension.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.dimension;
-
-import java.util.Objects;
-
-/**
- * Non-SI base dimensions.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public enum OtherBaseDimension implements BaseDimension {
- INFORMATION("Info"), CURRENCY("$$");
-
- /** The dimension's symbol */
- private final String symbol;
-
- /**
- * Creates the {@code SIBaseDimension}.
- *
- * @param symbol
- * dimension's symbol
- * @since 2018-12-11
- * @since v0.1.0
- */
- private OtherBaseDimension(final String symbol) {
- this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
- }
-
- @Override
- public String getName() {
- return this.toString();
- }
-
- @Override
- public String getSymbol() {
- return this.symbol;
- }
-}
diff --git a/src/unitConverter/dimension/SIBaseDimension.java b/src/unitConverter/dimension/SIBaseDimension.java
deleted file mode 100755
index 928d8d6..0000000
--- a/src/unitConverter/dimension/SIBaseDimension.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.dimension;
-
-import java.util.Objects;
-
-/**
- * The seven base dimensions that make up the SI.
- *
- * @author Adrien Hopkins
- * @since 2018-12-11
- * @since v0.1.0
- */
-public enum SIBaseDimension implements BaseDimension {
- LENGTH("L"), MASS("M"), TIME("T"), ELECTRIC_CURRENT("I"), TEMPERATURE("\u0398"), // u0398 is the theta symbol
- QUANTITY("N"), LUMINOUS_INTENSITY("J");
-
- /** The dimension's symbol */
- private final String symbol;
-
- /**
- * Creates the {@code SIBaseDimension}.
- *
- * @param symbol
- * dimension's symbol
- * @since 2018-12-11
- * @since v0.1.0
- */
- private SIBaseDimension(final String symbol) {
- this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
- }
-
- @Override
- public String getName() {
- return this.toString();
- }
-
- @Override
- public String getSymbol() {
- return this.symbol;
- }
-
-}
diff --git a/src/unitConverter/dimension/StandardDimensions.java b/src/unitConverter/dimension/StandardDimensions.java
deleted file mode 100755
index b3edb7d..0000000
--- a/src/unitConverter/dimension/StandardDimensions.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.dimension;
-
-/**
- * All of the dimensions that are used by the SI.
- *
- * @author Adrien Hopkins
- * @since 2018-12-11
- * @since v0.1.0
- */
-public final class StandardDimensions {
- // base dimensions
- public static final UnitDimension EMPTY = UnitDimension.EMPTY;
- public static final UnitDimension LENGTH = UnitDimension.getBase(SIBaseDimension.LENGTH);
- public static final UnitDimension MASS = UnitDimension.getBase(SIBaseDimension.MASS);
- public static final UnitDimension TIME = UnitDimension.getBase(SIBaseDimension.TIME);
- public static final UnitDimension ELECTRIC_CURRENT = UnitDimension.getBase(SIBaseDimension.ELECTRIC_CURRENT);
- public static final UnitDimension TEMPERATURE = UnitDimension.getBase(SIBaseDimension.TEMPERATURE);
- public static final UnitDimension QUANTITY = UnitDimension.getBase(SIBaseDimension.QUANTITY);
- public static final UnitDimension LUMINOUS_INTENSITY = UnitDimension.getBase(SIBaseDimension.LUMINOUS_INTENSITY);
- public static final UnitDimension INFORMATION = UnitDimension.getBase(OtherBaseDimension.INFORMATION);
- public static final UnitDimension CURRENCY = UnitDimension.getBase(OtherBaseDimension.CURRENCY);
- // derived dimensions without named SI units
- public static final UnitDimension AREA = LENGTH.times(LENGTH);
-
- public static final UnitDimension VOLUME = AREA.times(LENGTH);
- public static final UnitDimension VELOCITY = LENGTH.dividedBy(TIME);
- public static final UnitDimension ACCELERATION = VELOCITY.dividedBy(TIME);
- public static final UnitDimension WAVENUMBER = EMPTY.dividedBy(LENGTH);
- public static final UnitDimension MASS_DENSITY = MASS.dividedBy(VOLUME);
- public static final UnitDimension SURFACE_DENSITY = MASS.dividedBy(AREA);
- public static final UnitDimension SPECIFIC_VOLUME = VOLUME.dividedBy(MASS);
- public static final UnitDimension CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA);
- public static final UnitDimension MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH);
- public static final UnitDimension CONCENTRATION = QUANTITY.dividedBy(VOLUME);
- public static final UnitDimension MASS_CONCENTRATION = CONCENTRATION.times(MASS);
- public static final UnitDimension LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA);
- public static final UnitDimension REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY);
- public static final UnitDimension REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY);
- public static final UnitDimension ANGLE = LENGTH.dividedBy(LENGTH);
- public static final UnitDimension SOLID_ANGLE = AREA.dividedBy(AREA);
- // derived dimensions with named SI units
- public static final UnitDimension FREQUENCY = EMPTY.dividedBy(TIME);
-
- public static final UnitDimension FORCE = MASS.times(ACCELERATION);
- public static final UnitDimension ENERGY = FORCE.times(LENGTH);
- public static final UnitDimension POWER = ENERGY.dividedBy(TIME);
- public static final UnitDimension ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME);
- public static final UnitDimension VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE);
- public static final UnitDimension CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE);
- public static final UnitDimension ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT);
- public static final UnitDimension ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE);
- public static final UnitDimension MAGNETIC_FLUX = VOLTAGE.times(TIME);
- public static final UnitDimension MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA);
- public static final UnitDimension INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT);
- public static final UnitDimension LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE);
- public static final UnitDimension ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA);
- public static final UnitDimension SPECIFIC_ENERGY = ENERGY.dividedBy(MASS);
- public static final UnitDimension CATALYTIC_ACTIVITY = QUANTITY.dividedBy(TIME);
-
- // You may NOT get StandardDimensions instances!
- private StandardDimensions() {
- throw new AssertionError();
- }
-}
diff --git a/src/unitConverter/dimension/UnitDimension.java b/src/unitConverter/dimension/UnitDimension.java
deleted file mode 100755
index 40e5bbc..0000000
--- a/src/unitConverter/dimension/UnitDimension.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.dimension;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * An object that represents what a unit measures, like length, mass, area, energy, etc.
- *
- * @author Adrien Hopkins
- * @since 2018-12-11
- * @since v0.1.0
- */
-public final class UnitDimension {
- /**
- * The unit dimension where every exponent is zero
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- public static final UnitDimension EMPTY = new UnitDimension(new HashMap<>());
-
- /**
- * Gets an UnitDimension that has 1 of a certain dimension and nothing else
- *
- * @param dimension
- * dimension to get
- * @return unit dimension
- * @since 2018-12-11
- * @since v0.1.0
- */
- public static final UnitDimension getBase(final BaseDimension dimension) {
- final Map map = new HashMap<>();
- map.put(dimension, 1);
- return new UnitDimension(map);
- }
-
- /**
- * The base dimensions that make up this dimension.
- *
- * @since 2018-12-11
- * @since v0.1.0
- */
- final Map exponents;
-
- /**
- * Creates the {@code UnitDimension}.
- *
- * @param exponents
- * base dimensions that make up this dimension
- * @since 2018-12-11
- * @since v0.1.0
- */
- private UnitDimension(final Map exponents) {
- this.exponents = new HashMap<>(exponents);
- }
-
- /**
- * Divides this dimension by another
- *
- * @param other
- * other dimension
- * @return quotient of two dimensions
- * @since 2018-12-11
- * @since v0.1.0
- */
- public UnitDimension dividedBy(final UnitDimension other) {
- final Map map = new HashMap<>(this.exponents);
-
- for (final BaseDimension key : other.exponents.keySet()) {
- if (map.containsKey(key)) {
- // add the dimensions
- map.put(key, map.get(key) - other.exponents.get(key));
- } else {
- map.put(key, -other.exponents.get(key));
- }
- }
- return new UnitDimension(map);
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (!(obj instanceof UnitDimension))
- return false;
- final UnitDimension other = (UnitDimension) obj;
-
- // anything with a value of 0 is equal to a nonexistent value
- for (final BaseDimension b : this.getBaseSet()) {
- if (this.exponents.get(b) != other.exponents.get(b))
- if (!(this.exponents.get(b) == 0 && !other.exponents.containsKey(b)))
- return false;
- }
- for (final BaseDimension b : other.getBaseSet()) {
- if (this.exponents.get(b) != other.exponents.get(b))
- if (!(other.exponents.get(b) == 0 && !this.exponents.containsKey(b)))
- return false;
- }
- return true;
- }
-
- /**
- * @return a set of all of the base dimensions with non-zero exponents that make up this dimension.
- * @since 2018-12-12
- * @since v0.1.0
- */
- public final Set getBaseSet() {
- final Set dimensions = new HashSet<>();
-
- // add all dimensions with a nonzero exponent - they shouldn't be there in the first place
- for (final BaseDimension dimension : this.exponents.keySet()) {
- if (!this.exponents.get(dimension).equals(0)) {
- dimensions.add(dimension);
- }
- }
-
- return dimensions;
- }
-
- /**
- * Gets the exponent for a specific dimension.
- *
- * @param dimension
- * dimension to check
- * @return exponent for that dimension
- * @since 2018-12-12
- * @since v0.1.0
- */
- public int getExponent(final BaseDimension dimension) {
- return this.exponents.getOrDefault(dimension, 0);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(this.exponents);
- }
-
- /**
- * @return true if this dimension is a base, i.e. it has one exponent of one and no other nonzero exponents
- * @since 2019-01-15
- * @since v0.1.0
- */
- public boolean isBase() {
- int oneCount = 0;
- boolean twoOrMore = false; // has exponents of 2 or more
- for (final BaseDimension b : this.getBaseSet()) {
- if (this.exponents.get(b) == 1) {
- oneCount++;
- } else if (this.exponents.get(b) != 0) {
- twoOrMore = true;
- }
- }
- return (oneCount == 0 || oneCount == 1) && !twoOrMore;
- }
-
- /**
- * Multiplies this dimension by another
- *
- * @param other
- * other dimension
- * @return product of two dimensions
- * @since 2018-12-11
- * @since v0.1.0
- */
- public UnitDimension times(final UnitDimension other) {
- final Map map = new HashMap<>(this.exponents);
-
- for (final BaseDimension key : other.exponents.keySet()) {
- if (map.containsKey(key)) {
- // add the dimensions
- map.put(key, map.get(key) + other.exponents.get(key));
- } else {
- map.put(key, other.exponents.get(key));
- }
- }
- return new UnitDimension(map);
- }
-
- /**
- * Returns this dimension, but to an exponent
- *
- * @param exp
- * exponent
- * @return result of exponientation
- * @since 2019-01-15
- * @since v0.1.0
- */
- public UnitDimension toExponent(final int exp) {
- final Map map = new HashMap<>(this.exponents);
- for (final BaseDimension key : this.exponents.keySet()) {
- map.put(key, this.getExponent(key) * exp);
- }
- return new UnitDimension(map);
- }
-
- @Override
- public String toString() {
- final List positiveStringComponents = new ArrayList<>();
- final List negativeStringComponents = new ArrayList<>();
-
- // for each base dimension that makes up this dimension, add it and its exponent
- for (final BaseDimension dimension : this.getBaseSet()) {
- final int exponent = this.exponents.get(dimension);
- if (exponent > 0) {
- positiveStringComponents.add(String.format("%s^%d", dimension.getSymbol(), exponent));
- } else if (exponent < 0) {
- negativeStringComponents.add(String.format("%s^%d", dimension.getSymbol(), -exponent));
- }
- }
-
- final String positiveString = positiveStringComponents.isEmpty() ? "1"
- : String.join(" ", positiveStringComponents);
- final String negativeString = negativeStringComponents.isEmpty() ? ""
- : " / " + String.join(" ", negativeStringComponents);
-
- return positiveString + negativeString;
- }
-}
diff --git a/src/unitConverter/dimension/UnitDimensionTest.java b/src/unitConverter/dimension/UnitDimensionTest.java
deleted file mode 100755
index 86db1b8..0000000
--- a/src/unitConverter/dimension/UnitDimensionTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.dimension;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static unitConverter.dimension.StandardDimensions.AREA;
-import static unitConverter.dimension.StandardDimensions.ENERGY;
-import static unitConverter.dimension.StandardDimensions.LENGTH;
-import static unitConverter.dimension.StandardDimensions.MASS;
-import static unitConverter.dimension.StandardDimensions.MASS_DENSITY;
-import static unitConverter.dimension.StandardDimensions.QUANTITY;
-import static unitConverter.dimension.StandardDimensions.TIME;
-import static unitConverter.dimension.StandardDimensions.VOLUME;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests for {@link UnitDimension}.
- *
- * @author Adrien Hopkins
- * @since 2018-12-12
- * @since v0.1.0
- */
-class UnitDimensionTest {
- /**
- * Tests {@link UnitDimension#equals}
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- @Test
- void testEquals() {
- assertEquals(LENGTH, LENGTH);
- assertFalse(LENGTH.equals(QUANTITY));
- }
-
- /**
- * Tests {@code UnitDimension}'s exponentiation
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- @Test
- void testExponents() {
- assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
- assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
- }
-
- /**
- * Tests {@code UnitDimension}'s multiplication and division.
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- @Test
- void testMultiplicationAndDivision() {
- assertEquals(AREA, LENGTH.times(LENGTH));
- assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME));
- assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME));
- assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME));
- }
-}
diff --git a/src/unitConverter/dimension/package-info.java b/src/unitConverter/dimension/package-info.java
deleted file mode 100755
index 74895ce..0000000
--- a/src/unitConverter/dimension/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-/**
- * Everything to do with what a unit measures, or its dimension.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- */
-package unitConverter.dimension;
\ No newline at end of file
diff --git a/src/unitConverter/package-info.java b/src/unitConverter/package-info.java
deleted file mode 100644
index e2f7ff7..0000000
--- a/src/unitConverter/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-/**
- * A program that converts units.
- *
- * @author Adrien Hopkins
- * @since 2019-01-25
- */
-package unitConverter;
\ No newline at end of file
diff --git a/src/unitConverter/unit/AbstractUnit.java b/src/unitConverter/unit/AbstractUnit.java
deleted file mode 100644
index 24814e8..0000000
--- a/src/unitConverter/unit/AbstractUnit.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import java.util.Objects;
-
-import unitConverter.dimension.UnitDimension;
-
-/**
- * The default abstract implementation of the {@code Unit} interface.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-public abstract class AbstractUnit implements Unit {
- /**
- * The number of units created, including base units.
- *
- * @since 2019-01-02
- * @since v0.1.0
- */
- private static long unitCount = 0;
-
- /**
- * The number of base units created.
- *
- * @since 2019-01-02
- * @since v0.1.0
- */
- private static long baseUnitCount = 0;
-
- /**
- * @return number of base units created
- * @since 2019-01-02
- * @since v0.1.0
- */
- public static final long getBaseUnitCount() {
- return baseUnitCount;
- }
-
- /**
- * @return number of units created
- * @since 2019-01-02
- * @since v0.1.0
- */
- public static final long getUnitCount() {
- return unitCount;
- }
-
- /**
- * Increments the number of base units.
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public static final void incrementBaseUnitCounter() {
- baseUnitCount++;
- }
-
- /**
- * Increments the number of units.
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public static final void incrementUnitCounter() {
- unitCount++;
- }
-
- /**
- * The dimension, or what the unit measures.
- *
- * @since 2018-12-22
- * @since v0.1.0
- */
- private final UnitDimension dimension;
-
- /**
- * The unit's base unit. Values converted by {@code convertFromBase} and {@code convertToBase} are expressed in this
- * unit.
- *
- * @since 2018-12-22
- * @since v0.1.0
- */
- private final BaseUnit base;
-
- /**
- * The system that this unit is a part of.
- *
- * @since 2018-12-23
- * @since v0.1.0
- */
- private final UnitSystem system;
-
- /**
- * Creates the {@code AbstractUnit}.
- *
- * @param base
- * unit's base
- * @throws NullPointerException
- * if name, symbol or base is null
- * @since 2018-12-22
- * @since v0.1.0
- */
- public AbstractUnit(final BaseUnit base) {
- this.base = Objects.requireNonNull(base, "base must not be null.");
- this.dimension = this.base.getDimension();
- this.system = this.base.getSystem();
- }
-
- /**
- * Creates the {@code AbstractUnit} using a unique dimension. This constructor is for making base units and should
- * only be used by {@code BaseUnit}.
- *
- * @param dimension
- * dimension measured by unit
- * @param system
- * system that unit is a part of
- * @throws AssertionError
- * if this constructor is not run by {@code BaseUnit} or a subclass
- * @throws NullPointerException
- * if name, symbol or dimension is null
- * @since 2018-12-23
- * @since v0.1.0
- */
- AbstractUnit(final UnitDimension dimension, final UnitSystem system) {
- // try to set this as a base unit
- if (this instanceof BaseUnit) {
- this.base = (BaseUnit) this;
- } else
- throw new AssertionError();
-
- this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
- this.system = Objects.requireNonNull(system, "system must not be null.");
- }
-
- @Override
- public final BaseUnit getBase() {
- return this.base;
- }
-
- @Override
- public final UnitDimension getDimension() {
- return this.dimension;
- }
-
- @Override
- public final UnitSystem getSystem() {
- return this.system;
- }
-
- // TODO document and revise units' toString methods
- @Override
- public String toString() {
- return String.format("%s-derived unit of dimension %s", this.getSystem(), this.getDimension());
- }
-}
diff --git a/src/unitConverter/unit/BaseUnit.java b/src/unitConverter/unit/BaseUnit.java
deleted file mode 100755
index fe36c45..0000000
--- a/src/unitConverter/unit/BaseUnit.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import java.util.Objects;
-
-import 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.
- *
- * @author Adrien Hopkins
- * @since 2018-12-23
- * @since v0.1.0
- */
-public final class BaseUnit extends AbstractUnit {
- /**
- * Is this unit a full base (i.e. m, s, ... but not N, J, ...)
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- private final boolean isFullBase;
-
- /**
- * Creates the {@code BaseUnit}.
- *
- * @param dimension
- * dimension measured by unit
- * @param system
- * system that unit is a part of
- * @param name
- * name of unit
- * @param symbol
- * symbol of unit
- * @since 2018-12-23
- * @since v0.1.0
- */
- BaseUnit(final UnitDimension dimension, final UnitSystem system) {
- super(dimension, system);
- this.isFullBase = dimension.isBase();
- }
-
- /**
- * @return this unit as a {@code LinearUnit}
- * @since 2019-01-25
- * @since v0.1.0
- */
- public LinearUnit asLinearUnit() {
- return this.times(1);
- }
-
- @Override
- public double convertFromBase(final double value) {
- return value;
- }
-
- @Override
- public double convertToBase(final double value) {
- return value;
- }
-
- /**
- * Divides this unit by another unit.
- *
- * @param other
- * unit to divide by
- * @return quotient of two units
- * @throws IllegalArgumentException
- * if this unit's system is not other's system
- * @throws NullPointerException
- * if other is null
- * @since 2018-12-22
- * @since v0.1.0
- */
- public BaseUnit dividedBy(final BaseUnit other) {
- Objects.requireNonNull(other, "other must not be null.");
- if (!this.getSystem().equals(other.getSystem()))
- throw new IllegalArgumentException("Incompatible base units for division.");
- return new BaseUnit(this.getDimension().dividedBy(other.getDimension()), this.getSystem());
- }
-
- /**
- * Divides this unit by a divisor
- *
- * @param divisor
- * amount to divide by
- * @return quotient
- * @since 2018-12-23
- * @since v0.1.0
- */
- public LinearUnit dividedBy(final double divisor) {
- return new LinearUnit(this, 1 / divisor);
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (!(obj instanceof BaseUnit))
- return false;
- final BaseUnit other = (BaseUnit) obj;
- return Objects.equals(this.getSystem(), other.getSystem())
- && Objects.equals(this.getDimension(), other.getDimension());
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = result * prime + this.getSystem().hashCode();
- result = result * prime + this.getDimension().hashCode();
- return result;
- }
-
- /**
- * Multiplies this unit by another unit.
- *
- * @param other
- * unit to multiply by
- * @return product of two units
- * @throws IllegalArgumentException
- * if this unit's system is not other's system
- * @throws NullPointerException
- * if other is null
- * @since 2018-12-22
- * @since v0.1.0
- */
- public BaseUnit times(final BaseUnit other) {
- Objects.requireNonNull(other, "other must not be null.");
- if (!this.getSystem().equals(other.getSystem()))
- throw new IllegalArgumentException("Incompatible base units for multiplication.");
- return new BaseUnit(this.getDimension().times(other.getDimension()), this.getSystem());
- }
-
- /**
- * Multiplies this unit by a multiplier.
- *
- * @param multiplier
- * amount to multiply by
- * @return product
- * @since 2018-12-23
- * @since v0.1.0
- */
- public LinearUnit times(final double multiplier) {
- return new LinearUnit(this, multiplier);
- }
-
- /**
- * Returns this unit, but to an exponent.
- *
- * @param exponent
- * exponent
- * @return result of exponentiation
- * @since 2019-01-15
- * @since v0.1.0
- */
- public BaseUnit toExponent(final int exponent) {
- return this.getSystem().getBaseUnit(this.getDimension().toExponent(exponent));
- }
-
- @Override
- public String toString() {
- return String.format("%s base unit of%s dimension %s", this.getSystem(), this.isFullBase ? " base" : "",
- this.getDimension());
- }
-}
diff --git a/src/unitConverter/unit/DefaultUnitPrefix.java b/src/unitConverter/unit/DefaultUnitPrefix.java
deleted file mode 100755
index d19161b..0000000
--- a/src/unitConverter/unit/DefaultUnitPrefix.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import java.util.Objects;
-
-/**
- * The default implementation of {@code UnitPrefix}, which contains a multiplier and nothing else.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public final class DefaultUnitPrefix implements UnitPrefix {
- private final double multiplier;
-
- /**
- * Creates the {@code DefaultUnitPrefix}.
- *
- * @param multiplier
- * @since 2019-01-14
- */
- public DefaultUnitPrefix(final double multiplier) {
- this.multiplier = multiplier;
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (!(obj instanceof DefaultUnitPrefix))
- return false;
- final DefaultUnitPrefix other = (DefaultUnitPrefix) obj;
- return Double.doubleToLongBits(this.multiplier) == Double.doubleToLongBits(other.multiplier);
- }
-
- @Override
- public double getMultiplier() {
- return this.multiplier;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(this.multiplier);
- }
-
- @Override
- public String toString() {
- return String.format("Unit prefix equal to %s", this.multiplier);
- }
-}
diff --git a/src/unitConverter/unit/LinearUnit.java b/src/unitConverter/unit/LinearUnit.java
deleted file mode 100644
index b786b3b..0000000
--- a/src/unitConverter/unit/LinearUnit.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import java.util.Objects;
-
-import unitConverter.dimension.UnitDimension;
-
-/**
- * A unit that is equal to a certain number multiplied by its base.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-public final class LinearUnit extends AbstractUnit {
- /**
- * The value of one of this unit in this unit's base unit
- *
- * @since 2018-12-22
- * @since v0.1.0
- */
- private final double conversionFactor;
-
- /**
- *
- * Creates the {@code LinearUnit}.
- *
- * @param base
- * unit's base
- * @param conversionFactor
- * value of one of this unit in its base
- * @since 2018-12-23
- * @since v0.1.0
- */
- LinearUnit(final BaseUnit base, final double conversionFactor) {
- super(base);
- this.conversionFactor = conversionFactor;
- }
-
- /**
- * Creates the {@code LinearUnit} as a base unit.
- *
- * @param dimension
- * dimension measured by unit
- * @param system
- * system unit is part of
- * @since 2019-01-25
- * @since v0.1.0
- */
- LinearUnit(final UnitDimension dimension, final UnitSystem system, final double conversionFactor) {
- super(dimension, system);
- this.conversionFactor = conversionFactor;
- }
-
- @Override
- public double convertFromBase(final double value) {
- return value / this.getConversionFactor();
- }
-
- @Override
- public double convertToBase(final double value) {
- return value * this.getConversionFactor();
- }
-
- /**
- * Divides this unit by a scalar.
- *
- * @param divisor
- * scalar to divide by
- * @return quotient
- * @since 2018-12-23
- * @since v0.1.0
- */
- public LinearUnit dividedBy(final double divisor) {
- return new LinearUnit(this.getBase(), this.getConversionFactor() / divisor);
- }
-
- /**
- * Divides this unit by another unit.
- *
- * @param other
- * unit to divide by
- * @return quotient of two units
- * @throws NullPointerException
- * if other is null
- * @since 2018-12-22
- * @since v0.1.0
- */
- public LinearUnit dividedBy(final LinearUnit other) {
- Objects.requireNonNull(other, "other must not be null");
- final BaseUnit base = this.getBase().dividedBy(other.getBase());
- return new LinearUnit(base, this.getConversionFactor() / other.getConversionFactor());
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (!(obj instanceof LinearUnit))
- return false;
- final LinearUnit other = (LinearUnit) obj;
- return Objects.equals(this.getBase(), other.getBase())
- && Objects.equals(this.getConversionFactor(), other.getConversionFactor());
- }
-
- /**
- * @return conversionFactor
- * @since 2018-12-22
- * @since v0.1.0
- */
- public final double getConversionFactor() {
- return this.conversionFactor;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = result * prime + this.getBase().hashCode();
- result = result * prime + Double.hashCode(this.getConversionFactor());
- return result;
- }
-
- /**
- * Multiplies this unit by a scalar.
- *
- * @param multiplier
- * scalar to multiply by
- * @return product
- * @since 2018-12-23
- * @since v0.1.0
- */
- public LinearUnit times(final double multiplier) {
- return new LinearUnit(this.getBase(), this.getConversionFactor() * multiplier);
- }
-
- /**
- * Multiplies this unit by another unit.
- *
- * @param other
- * unit to multiply by=
- * @return product of two units
- * @throws NullPointerException
- * if other is null
- * @since 2018-12-22
- * @since v0.1.0
- */
- public LinearUnit times(final LinearUnit other) {
- Objects.requireNonNull(other, "other must not be null");
- final BaseUnit base = this.getBase().times(other.getBase());
- return new LinearUnit(base, this.getConversionFactor() * other.getConversionFactor());
- }
-
- /**
- * Returns this unit but to an exponent.
- *
- * @param exponent
- * exponent to exponientate unit to
- * @return exponientated unit
- * @since 2019-01-15
- * @since v0.1.0
- */
- public LinearUnit toExponent(final int exponent) {
- return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
- }
-
- @Override
- public String toString() {
- return super.toString() + String.format(" (equal to %s * base)", this.getConversionFactor());
- }
-}
diff --git a/src/unitConverter/unit/NonlinearUnits.java b/src/unitConverter/unit/NonlinearUnits.java
deleted file mode 100755
index ec1874c..0000000
--- a/src/unitConverter/unit/NonlinearUnits.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-/**
- * Some major nonlinear units.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public final class NonlinearUnits {
- public static final Unit CELSIUS = new AbstractUnit(SI.KELVIN) {
-
- @Override
- public double convertFromBase(final double value) {
- return value - 273.15;
- }
-
- @Override
- public double convertToBase(final double value) {
- return value + 273.15;
- }
- };
-
- public static final Unit FAHRENHEIT = new AbstractUnit(SI.KELVIN) {
-
- @Override
- public double convertFromBase(final double value) {
- return 1.8 * value - 459.67;
- }
-
- @Override
- public double convertToBase(final double value) {
- return (value + 459.67) / 1.8;
- }
- };
-
- // You may NOT get a NonlinearUnits instance.
- private NonlinearUnits() {
- throw new AssertionError();
- }
-}
diff --git a/src/unitConverter/unit/SI.java b/src/unitConverter/unit/SI.java
deleted file mode 100644
index 54eb4c5..0000000
--- a/src/unitConverter/unit/SI.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-import unitConverter.dimension.StandardDimensions;
-import unitConverter.dimension.UnitDimension;
-
-/**
- * The SI, which holds all SI units
- *
- * @author Adrien Hopkins
- * @since 2018-12-23
- * @since v0.1.0
- */
-public enum SI implements UnitSystem {
- SI;
-
- /**
- * This system's base units.
- *
- * @since 2019-01-25
- * @since v0.1.0
- */
- private static final Set baseUnits = new HashSet<>();
-
- // base units
- public static final BaseUnit METRE = SI.getBaseUnit(StandardDimensions.LENGTH);
- public static final BaseUnit KILOGRAM = SI.getBaseUnit(StandardDimensions.MASS);
- public static final BaseUnit SECOND = SI.getBaseUnit(StandardDimensions.TIME);
- public static final BaseUnit AMPERE = SI.getBaseUnit(StandardDimensions.ELECTRIC_CURRENT);
- public static final BaseUnit KELVIN = SI.getBaseUnit(StandardDimensions.TEMPERATURE);
- public static final BaseUnit MOLE = SI.getBaseUnit(StandardDimensions.QUANTITY);
- public static final BaseUnit CANDELA = SI.getBaseUnit(StandardDimensions.LUMINOUS_INTENSITY);
-
- @Override
- public BaseUnit getBaseUnit(final UnitDimension dimension) {
- // try to find an existing unit before creating a new one
-
- Objects.requireNonNull(dimension, "dimension must not be null.");
- for (final BaseUnit unit : baseUnits) {
- // it will be equal since the conditions for equality are dimension and system,
- // and system is always SI.
- if (unit.getDimension().equals(dimension))
- return unit;
- }
- // could not find an existing base unit
- final BaseUnit unit = new BaseUnit(dimension, this);
- baseUnits.add(unit);
- return unit;
- }
-
- @Override
- public String getName() {
- return "SI";
- }
-}
diff --git a/src/unitConverter/unit/SIPrefix.java b/src/unitConverter/unit/SIPrefix.java
deleted file mode 100755
index 54625fb..0000000
--- a/src/unitConverter/unit/SIPrefix.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-/**
- * The SI prefixes.
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public enum SIPrefix implements UnitPrefix {
- DECA(10), HECTO(100), KILO(1e3), MEGA(1e6), GIGA(1e9), TERA(1e12), PETA(1e15), EXA(1e18), ZETTA(1e21), YOTTA(
- 1e24), DECI(0.1), CENTI(0.01), MILLI(
- 1e-3), MICRO(1e-6), NANO(1e-9), PICO(1e-12), FEMTO(1e-15), ATTO(1e-18), ZEPTO(1e-21), YOCTO(1e-24);
-
- private final double multiplier;
-
- /**
- * Creates the {@code SIPrefix}.
- *
- * @param multiplier
- * prefix's multiplier
- * @since 2019-01-14
- * @since v0.1.0
- */
- private SIPrefix(final double multiplier) {
- this.multiplier = multiplier;
- }
-
- /**
- * @return value
- * @since 2019-01-14
- * @since v0.1.0
- */
- @Override
- public final double getMultiplier() {
- return this.multiplier;
- }
-}
diff --git a/src/unitConverter/unit/Unit.java b/src/unitConverter/unit/Unit.java
deleted file mode 100755
index 54f1423..0000000
--- a/src/unitConverter/unit/Unit.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import java.util.Objects;
-
-import unitConverter.dimension.UnitDimension;
-
-/**
- * A unit that has an associated base unit, and can convert a value expressed in it to and from that base.
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- * @since v0.1.0
- */
-public interface Unit {
- /**
- * Checks if a value expressed in this unit can be converted to a value expressed in {@code other}
- *
- * @param other
- * unit to test with
- * @return true if the units are compatible
- * @since 2019-01-13
- * @since v0.1.0
- */
- default boolean canConvertTo(final Unit other) {
- return Objects.equals(this.getBase(), other.getBase());
- }
-
- /**
- * Converts from a value expressed in this unit's base unit to a value expressed in this unit.
- *
- * This must be the inverse of {@code convertToBase}, so {@code convertFromBase(convertToBase(value))} must be equal
- * to {@code value} for any value, ignoring precision loss by roundoff error.
- *
- *
- * If this unit is a base unit, this method should return {@code value}.
- *
- *
- * @param value
- * value expressed in base unit
- * @return value expressed in this unit
- * @since 2018-12-22
- * @since v0.1.0
- */
- double convertFromBase(double value);
-
- /**
- * Converts from a value expressed in this unit to a value expressed in this unit's base unit.
- *
- * This must be the inverse of {@code convertFromBase}, so {@code convertToBase(convertFromBase(value))} must be
- * equal to {@code value} for any value, ignoring precision loss by roundoff error.
- *
- *
- * If this unit is a base unit, this method should return {@code value}.
- *
- *
- * @param value
- * value expressed in this unit
- * @return value expressed in base unit
- * @since 2018-12-22
- * @since v0.1.0
- */
- double convertToBase(double value);
-
- /**
- *
- * Returns the base unit associated with this unit.
- *
- *
- * The dimension of this unit must be equal to the dimension of the returned unit.
- *
- *
- * If this unit is a base unit, this method should return this unit.\
- *
- *
- * @return base unit associated with this unit
- * @since 2018-12-22
- * @since v0.1.0
- */
- BaseUnit getBase();
-
- /**
- * @return dimension measured by this unit
- * @since 2018-12-22
- * @since v0.1.0
- */
- UnitDimension getDimension();
-
- /**
- * @return system that this unit is a part of
- * @since 2018-12-23
- * @since v0.1.0
- */
- UnitSystem getSystem();
-}
diff --git a/src/unitConverter/unit/UnitPrefix.java b/src/unitConverter/unit/UnitPrefix.java
deleted file mode 100755
index a0c1b7c..0000000
--- a/src/unitConverter/unit/UnitPrefix.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-/**
- * A prefix that can be attached onto the front of any unit, which multiplies it by a certain value
- *
- * @author Adrien Hopkins
- * @since 2019-01-14
- * @since v0.1.0
- */
-public interface UnitPrefix {
- /**
- * @return this prefix's multiplier
- * @since 2019-01-14
- * @since v0.1.0
- */
- double getMultiplier();
-}
diff --git a/src/unitConverter/unit/UnitSystem.java b/src/unitConverter/unit/UnitSystem.java
deleted file mode 100755
index ce8c249..0000000
--- a/src/unitConverter/unit/UnitSystem.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import java.util.Objects;
-
-import unitConverter.dimension.UnitDimension;
-
-/**
- * A system of units. Each unit should be aware of its system.
- *
- * @author Adrien Hopkins
- * @since 2018-12-23
- * @since v0.1.0
- */
-public interface UnitSystem {
- /**
- * Gets a base unit for this system and the provided dimension.
- *
- * @param dimension
- * dimension used by base unit
- * @return base unit
- * @throws NullPointerException
- * if dimension is null
- * @since 2019-01-25
- * @since v0.1.0
- */
- default BaseUnit getBaseUnit(final UnitDimension dimension) {
- Objects.requireNonNull(dimension, "dimension must not be null.");
- return new BaseUnit(dimension, this);
- }
-
- /**
- * @return name of system
- * @since 2018-12-23
- * @since v0.1.0
- */
- String getName();
-}
diff --git a/src/unitConverter/unit/UnitTest.java b/src/unitConverter/unit/UnitTest.java
deleted file mode 100755
index c3237eb..0000000
--- a/src/unitConverter/unit/UnitTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package unitConverter.unit;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-import unitConverter.dimension.StandardDimensions;
-
-/**
- * Testing the various Unit classes
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- */
-class UnitTest {
- @Test
- void testConversion() {
- final BaseUnit metre = SI.METRE;
- final Unit inch = metre.times(0.0254);
-
- assertEquals(1.9, inch.convertToBase(75), 0.01);
- }
-
- @Test
- void testEquals() {
- final BaseUnit metre = SI.METRE;
- final Unit meter = SI.SI.getBaseUnit(StandardDimensions.LENGTH);
-
- assertEquals(metre, meter);
- }
-}
diff --git a/src/unitConverter/unit/package-info.java b/src/unitConverter/unit/package-info.java
deleted file mode 100644
index b5deb0c..0000000
--- a/src/unitConverter/unit/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-/**
- * All of the classes that correspond to the units being converted.
- *
- * @author Adrien Hopkins
- * @since 2019-01-25
- */
-package unitConverter.unit;
\ No newline at end of file
--
cgit v1.2.3
From 62a80fed0bf3bce2a66f9b786561a1389cd95f16 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 16 Mar 2019 15:01:59 -0400
Subject: Configured tests and moved them to src/test/java
---
.../unitConverter/dimension/UnitDimensionTest.java | 77 ---------------------
src/org/unitConverter/unit/UnitTest.java | 46 -------------
src/test/java/UnitDimensionTest.java | 79 ++++++++++++++++++++++
src/test/java/UnitTest.java | 49 ++++++++++++++
src/test/java/package-info.java | 23 +++++++
5 files changed, 151 insertions(+), 123 deletions(-)
delete mode 100755 src/org/unitConverter/dimension/UnitDimensionTest.java
delete mode 100755 src/org/unitConverter/unit/UnitTest.java
create mode 100755 src/test/java/UnitDimensionTest.java
create mode 100755 src/test/java/UnitTest.java
create mode 100644 src/test/java/package-info.java
diff --git a/src/org/unitConverter/dimension/UnitDimensionTest.java b/src/org/unitConverter/dimension/UnitDimensionTest.java
deleted file mode 100755
index 3b09610..0000000
--- a/src/org/unitConverter/dimension/UnitDimensionTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package org.unitConverter.dimension;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.unitConverter.dimension.StandardDimensions.AREA;
-import static org.unitConverter.dimension.StandardDimensions.ENERGY;
-import static org.unitConverter.dimension.StandardDimensions.LENGTH;
-import static org.unitConverter.dimension.StandardDimensions.MASS;
-import static org.unitConverter.dimension.StandardDimensions.MASS_DENSITY;
-import static org.unitConverter.dimension.StandardDimensions.QUANTITY;
-import static org.unitConverter.dimension.StandardDimensions.TIME;
-import static org.unitConverter.dimension.StandardDimensions.VOLUME;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link UnitDimension}.
- *
- * @author Adrien Hopkins
- * @since 2018-12-12
- * @since v0.1.0
- */
-class UnitDimensionTest {
- /**
- * Tests {@link UnitDimension#equals}
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- @Test
- void testEquals() {
- assertEquals(LENGTH, LENGTH);
- assertFalse(LENGTH.equals(QUANTITY));
- }
-
- /**
- * Tests {@code UnitDimension}'s exponentiation
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- @Test
- void testExponents() {
- assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
- assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
- }
-
- /**
- * Tests {@code UnitDimension}'s multiplication and division.
- *
- * @since 2018-12-12
- * @since v0.1.0
- */
- @Test
- void testMultiplicationAndDivision() {
- assertEquals(AREA, LENGTH.times(LENGTH));
- assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME));
- assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME));
- assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME));
- }
-}
diff --git a/src/org/unitConverter/unit/UnitTest.java b/src/org/unitConverter/unit/UnitTest.java
deleted file mode 100755
index 931cc57..0000000
--- a/src/org/unitConverter/unit/UnitTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package org.unitConverter.unit;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.unitConverter.dimension.StandardDimensions;
-
-/**
- * Testing the various Unit classes
- *
- * @author Adrien Hopkins
- * @since 2018-12-22
- */
-class UnitTest {
- @Test
- void testConversion() {
- final BaseUnit metre = SI.METRE;
- final Unit inch = metre.times(0.0254);
-
- assertEquals(1.9, inch.convertToBase(75), 0.01);
- }
-
- @Test
- void testEquals() {
- final BaseUnit metre = SI.METRE;
- final Unit meter = SI.SI.getBaseUnit(StandardDimensions.LENGTH);
-
- assertEquals(metre, meter);
- }
-}
diff --git a/src/test/java/UnitDimensionTest.java b/src/test/java/UnitDimensionTest.java
new file mode 100755
index 0000000..0b5055b
--- /dev/null
+++ b/src/test/java/UnitDimensionTest.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package test.java;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.unitConverter.dimension.StandardDimensions.AREA;
+import static org.unitConverter.dimension.StandardDimensions.ENERGY;
+import static org.unitConverter.dimension.StandardDimensions.LENGTH;
+import static org.unitConverter.dimension.StandardDimensions.MASS;
+import static org.unitConverter.dimension.StandardDimensions.MASS_DENSITY;
+import static org.unitConverter.dimension.StandardDimensions.QUANTITY;
+import static org.unitConverter.dimension.StandardDimensions.TIME;
+import static org.unitConverter.dimension.StandardDimensions.VOLUME;
+
+import org.junit.Test;
+import org.unitConverter.dimension.SIBaseDimension;
+import org.unitConverter.dimension.UnitDimension;
+
+/**
+ * Tests for {@link UnitDimension}.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+public class UnitDimensionTest {
+ /**
+ * Tests {@link UnitDimension#equals}
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ @Test
+ public void testEquals() {
+ assertEquals(LENGTH, LENGTH);
+ assertFalse(LENGTH.equals(QUANTITY));
+ }
+
+ /**
+ * Tests {@code UnitDimension}'s exponentiation
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ @Test
+ public void testExponents() {
+ assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
+ assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
+ }
+
+ /**
+ * Tests {@code UnitDimension}'s multiplication and division.
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ @Test
+ public void testMultiplicationAndDivision() {
+ assertEquals(AREA, LENGTH.times(LENGTH));
+ assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME));
+ assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME));
+ assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME));
+ }
+}
diff --git a/src/test/java/UnitTest.java b/src/test/java/UnitTest.java
new file mode 100755
index 0000000..45f890f
--- /dev/null
+++ b/src/test/java/UnitTest.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package test.java;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.unitConverter.dimension.StandardDimensions;
+import org.unitConverter.unit.BaseUnit;
+import org.unitConverter.unit.SI;
+import org.unitConverter.unit.Unit;
+
+/**
+ * Testing the various Unit classes
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ */
+public class UnitTest {
+ @Test
+ public void testConversion() {
+ final BaseUnit metre = SI.METRE;
+ final Unit inch = metre.times(0.0254);
+
+ assertEquals(1.9, inch.convertToBase(75), 0.01);
+ }
+
+ @Test
+ public void testEquals() {
+ final BaseUnit metre = SI.METRE;
+ final Unit meter = SI.SI.getBaseUnit(StandardDimensions.LENGTH);
+
+ assertEquals(metre, meter);
+ }
+}
diff --git a/src/test/java/package-info.java b/src/test/java/package-info.java
new file mode 100644
index 0000000..9f2e1d6
--- /dev/null
+++ b/src/test/java/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * All of the Unit Converter tests.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-03-16
+ */
+package test.java;
\ No newline at end of file
--
cgit v1.2.3
From 5f06f688ee0de31125682a9a0b1d05b4b5edf66c Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 16 Mar 2019 15:09:07 -0400
Subject: Updated changelog and fixed tests.
---
CHANGELOG.org | 3 +++
pom.xml | 7 +++++++
2 files changed, 10 insertions(+)
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 280ceb3..b9c87c9 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -2,6 +2,9 @@
All notable changes in this project will be shown in this file.
** Unreleased
+*** Changed
+ - Moved project to Maven
+ - Downgraded JUnit to 4.11
*** Added
- GUI for a selection-based unit converter
- The UnitDatabase now stores dimensions.
diff --git a/pom.xml b/pom.xml
index 3aede3b..8b3bc0d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,13 @@
1.8
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ false
+
+ org.codehaus.mojoexec-maven-plugin
--
cgit v1.2.3
From 6dbd32cd208c164e9c818b48b0b9bf823a152d71 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 17 Mar 2019 20:04:56 -0400
Subject: Added an expression parser that can parse RPN expressions.
---
.gitignore | 1 +
CHANGELOG.org | 1 +
.../expressionParser/ExpressionParser.java | 398 +++++++++++++++++++++
.../expressionParser/package-info.java | 23 ++
4 files changed, 423 insertions(+)
create mode 100644 src/org/unitConverter/expressionParser/ExpressionParser.java
create mode 100644 src/org/unitConverter/expressionParser/package-info.java
diff --git a/.gitignore b/.gitignore
index 866d01d..52a523a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
bin/
+target/
*.class
*~
\ No newline at end of file
diff --git a/CHANGELOG.org b/CHANGELOG.org
index b9c87c9..87e26e0 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -8,6 +8,7 @@ All notable changes in this project will be shown in this file.
*** Added
- GUI for a selection-based unit converter
- The UnitDatabase now stores dimensions.
+ - A system to parse mathematical expressions, used to parse unit expressions.
** v0.1.0
NOTE: At this stage, the API is subject to significant change.
*** Added
diff --git a/src/org/unitConverter/expressionParser/ExpressionParser.java b/src/org/unitConverter/expressionParser/ExpressionParser.java
new file mode 100644
index 0000000..804ea87
--- /dev/null
+++ b/src/org/unitConverter/expressionParser/ExpressionParser.java
@@ -0,0 +1,398 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.expressionParser;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+
+/**
+ * An object that can parse expressions with unary or binary operators.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of object that exists in parsed expressions
+ * @since 2019-03-14
+ */
+// TODO: possibly make this class non-final?
+public final class ExpressionParser {
+ /**
+ * A builder that can create {@code ExpressionParser} instances.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of object that exists in parsed expressions
+ * @since 2019-03-17
+ */
+ public static final class Builder {
+ /**
+ * A function that obtains a parseable object from a string. For example, an integer {@code ExpressionParser}
+ * would use {@code Integer::parseInt}.
+ *
+ * @since 2019-03-14
+ */
+ private final Function objectObtainer;
+
+ /**
+ * A map mapping operator strings to operator functions, for unary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> unaryOperators;
+
+ /**
+ * A map mapping operator strings to operator functions, for binary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> binaryOperators;
+
+ /**
+ * Creates the {@code Builder}.
+ *
+ * @param objectObtainer
+ * a function that can turn strings into objects of the type handled by the parser.
+ * @throws NullPointerException
+ * if {@code objectObtainer} is null
+ * @since 2019-03-17
+ */
+ public Builder(final Function objectObtainer) {
+ this.objectObtainer = Objects.requireNonNull(objectObtainer, "objectObtainer must not be null.");
+ this.unaryOperators = new HashMap<>();
+ this.binaryOperators = new HashMap<>();
+ }
+
+ /**
+ * Adds a binary operator to the builder.
+ *
+ * @param text
+ * text used to reference the operator, like '+'
+ * @param operator
+ * operator to add
+ * @param priority
+ * operator's priority, which determines which operators are applied first
+ * @return this builder
+ * @throws NullPointerException
+ * if {@code text} or {@code operator} is null
+ * @since 2019-03-17
+ */
+ public Builder addBinaryOperator(final String text, final BinaryOperator operator, final int priority) {
+ Objects.requireNonNull(text, "text must not be null.");
+ Objects.requireNonNull(operator, "operator must not be null.");
+
+ // Unfortunately, I cannot use a lambda because the PriorityBinaryOperator requires arguments.
+ final PriorityBinaryOperator priorityOperator = new PriorityBinaryOperator(priority) {
+ @Override
+ public T apply(final T t, final T u) {
+ return operator.apply(t, u);
+ }
+
+ };
+ this.binaryOperators.put(text, priorityOperator);
+ return this;
+ }
+
+ /**
+ * Adds a unary operator to the builder.
+ *
+ * @param text
+ * text used to reference the operator, like '-'
+ * @param operator
+ * operator to add
+ * @param priority
+ * operator's priority, which determines which operators are applied first
+ * @return this builder
+ * @throws NullPointerException
+ * if {@code text} or {@code operator} is null
+ * @since 2019-03-17
+ */
+ public Builder addUnaryOperator(final String text, final UnaryOperator operator, final int priority) {
+ Objects.requireNonNull(text, "text must not be null.");
+ Objects.requireNonNull(operator, "operator must not be null.");
+
+ // Unfortunately, I cannot use a lambda because the PriorityUnaryOperator requires arguments.
+ final PriorityUnaryOperator priorityOperator = new PriorityUnaryOperator(priority) {
+ @Override
+ public T apply(final T t) {
+ return operator.apply(t);
+ }
+ };
+ this.unaryOperators.put(text, priorityOperator);
+ return this;
+ }
+
+ /**
+ * @return an {@code ExpressionParser} instance with the properties given to this builder
+ * @since 2019-03-17
+ */
+ public ExpressionParser build() {
+ return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, this.binaryOperators);
+ }
+ }
+
+ /**
+ * A binary operator with a priority field that determines which operators apply first.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of operand and result
+ * @since 2019-03-17
+ */
+ private static abstract class PriorityBinaryOperator
+ implements BinaryOperator, Comparable> {
+ /**
+ * The operator's priority. Higher-priority operators are applied before lower-priority operators
+ */
+ private final int priority;
+
+ /**
+ * Creates the {@code PriorityBinaryOperator}.
+ *
+ * @param priority
+ * operator's priority
+ * @since 2019-03-17
+ */
+ public PriorityBinaryOperator(final int priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * Compares this object to another by priority.
+ */
+ @Override
+ public int compareTo(final PriorityBinaryOperator o) {
+ if (this.priority < o.priority)
+ return -1;
+ else if (this.priority > o.priority)
+ return 1;
+ else
+ return 0;
+ }
+ }
+
+ /**
+ * A unary operator with a priority field that determines which operators apply first.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of operand and result
+ * @since 2019-03-17
+ */
+ private static abstract class PriorityUnaryOperator
+ implements UnaryOperator, Comparable> {
+ /**
+ * The operator's priority. Higher-priority operators are applied before lower-priority operators
+ */
+ private final int priority;
+
+ /**
+ * Creates the {@code PriorityUnaryOperator}.
+ *
+ * @param priority
+ * operator's priority
+ * @since 2019-03-17
+ */
+ public PriorityUnaryOperator(final int priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * Compares this object to another by priority.
+ */
+ @Override
+ public int compareTo(final PriorityUnaryOperator o) {
+ if (this.priority < o.priority)
+ return -1;
+ else if (this.priority > o.priority)
+ return 1;
+ else
+ return 0;
+ }
+ }
+
+ /**
+ * The types of tokens that are available.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-03-14
+ */
+ private static enum TokenType {
+ OBJECT, UNARY_OPERATOR, BINARY_OPERATOR;
+ }
+
+ /**
+ * A function that obtains a parseable object from a string. For example, an integer {@code ExpressionParser} would
+ * use {@code Integer::parseInt}.
+ *
+ * @since 2019-03-14
+ */
+ private final Function objectObtainer;
+
+ /**
+ * A map mapping operator strings to operator functions, for unary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> unaryOperators;
+
+ /**
+ * A map mapping operator strings to operator functions, for binary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> binaryOperators;
+
+ /**
+ * Creates the {@code ExpressionParser}.
+ *
+ * @param objectObtainer
+ * function to get objects from strings
+ * @param unaryOperators
+ * operators available to the parser
+ * @since 2019-03-14
+ */
+ private ExpressionParser(final Function objectObtainer,
+ final Map> unaryOperators,
+ final Map> binaryOperators) {
+ this.objectObtainer = objectObtainer;
+ this.unaryOperators = unaryOperators;
+ this.binaryOperators = binaryOperators;
+ }
+
+ /**
+ * Converts a given mathematical expression to reverse Polish notation (operators after operands).
+ *
+ * For example,
+ * {@code 2 * (3 + 4)}
+ * becomes
+ * {@code 2 3 4 + *}.
+ *
+ * @param expression
+ * @return
+ * @since 2019-03-17
+ */
+ private String convertExpressionToReversePolish(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // TODO method stub org.unitConverter.expressionParser.ExpressionParser.convertExpressionToPolish(expression)
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Determines whether an inputted string is an object or an operator
+ *
+ * @param token
+ * string to input
+ * @return type of token it is
+ * @throws NullPointerException
+ * if {@code expression} is null
+ * @since 2019-03-14
+ */
+ private TokenType getTokenType(final String token) {
+ Objects.requireNonNull(token, "token must not be null.");
+
+ if (this.unaryOperators.containsKey(token))
+ return TokenType.UNARY_OPERATOR;
+ else if (this.binaryOperators.containsKey(token))
+ return TokenType.BINARY_OPERATOR;
+ else
+ return TokenType.OBJECT;
+ }
+
+ /**
+ * Parses an expression.
+ *
+ * @param expression
+ * expression to parse
+ * @return result
+ * @throws NullPointerException
+ * if {@code expression} is null
+ * @since 2019-03-14
+ */
+ public T parseExpression(final String expression) {
+ return this.parseReversePolishExpression(this.convertExpressionToReversePolish(expression));
+ }
+
+ /**
+ * Parses an expression expressed in reverse Polish notation.
+ *
+ * @param expression
+ * expression to parse
+ * @return result
+ * @throws NullPointerException
+ * if {@code expression} is null
+ * @since 2019-03-14
+ */
+ private T parseReversePolishExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ final Deque stack = new ArrayDeque<>();
+
+ // iterate over every item in the expression, then
+ for (final String item : expression.split(" ")) {
+ // choose a path based on what kind of thing was just read
+ switch (this.getTokenType(item)) {
+
+ case BINARY_OPERATOR:
+ if (stack.size() < 2)
+ throw new IllegalStateException(String.format(
+ "Attempted to call binary operator %s with only %d arguments.", item, stack.size()));
+
+ // get two arguments and operator, then apply!
+ final T o1 = stack.pop();
+ final T o2 = stack.pop();
+ final BinaryOperator binaryOperator = this.binaryOperators.get(item);
+
+ stack.push(binaryOperator.apply(o1, o2));
+ break;
+
+ case OBJECT:
+ // just add it to the stack
+ stack.push(this.objectObtainer.apply(item));
+ break;
+
+ case UNARY_OPERATOR:
+ if (stack.size() < 1)
+ throw new IllegalStateException(String.format(
+ "Attempted to call unary operator %s with only %d arguments.", item, stack.size()));
+
+ // get one argument and operator, then apply!
+ final T o = stack.pop();
+ final UnaryOperator unaryOperator = this.unaryOperators.get(item);
+
+ stack.push(unaryOperator.apply(o));
+ break;
+ default:
+ throw new AssertionError(
+ String.format("Internal error: Invalid token type %s.", this.getTokenType(item)));
+
+ }
+ }
+
+ // return answer, or throw an exception if I can't
+ if (stack.size() > 1)
+ throw new IllegalStateException("Computation ended up with more than one answer.");
+ else if (stack.size() == 0)
+ throw new IllegalStateException("Computation ended up without an answer.");
+ return stack.pop();
+ }
+}
diff --git a/src/org/unitConverter/expressionParser/package-info.java b/src/org/unitConverter/expressionParser/package-info.java
new file mode 100644
index 0000000..28f0cae
--- /dev/null
+++ b/src/org/unitConverter/expressionParser/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * A module that is capable of parsing expressions of things, like mathematical expressions or unit expressions.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-03-14
+ */
+package org.unitConverter.expressionParser;
\ No newline at end of file
--
cgit v1.2.3
From ea940f2c5b6450231ff9ce61f4b6704babdb0d9e Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Mon, 18 Mar 2019 16:43:36 -0400
Subject: Created an OperatableUnit interface for units that can operate.
---
src/org/unitConverter/unit/BaseUnit.java | 48 ++++++-
src/org/unitConverter/unit/LinearUnit.java | 57 ++++++++-
src/org/unitConverter/unit/OperatableUnit.java | 169 +++++++++++++++++++++++++
3 files changed, 272 insertions(+), 2 deletions(-)
create mode 100644 src/org/unitConverter/unit/OperatableUnit.java
diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
index 1f0c825..894d338 100755
--- a/src/org/unitConverter/unit/BaseUnit.java
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -28,7 +28,7 @@ import org.unitConverter.dimension.UnitDimension;
* @since 2018-12-23
* @since v0.1.0
*/
-public final class BaseUnit extends AbstractUnit {
+public final class BaseUnit extends AbstractUnit implements OperatableUnit {
/**
* Is this unit a full base (i.e. m, s, ... but not N, J, ...)
*
@@ -126,6 +126,35 @@ public final class BaseUnit extends AbstractUnit {
return result;
}
+ @Override
+ public LinearUnit negated() {
+ return this.times(-1);
+ }
+
+ @Override
+ public OperatableUnit plus(final OperatableUnit addend) {
+ Objects.requireNonNull(addend, "addend must not be null.");
+
+ // reject addends that cannot be added to this unit
+ if (!this.getSystem().equals(addend.getSystem()))
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
+ if (!this.getDimension().equals(addend.getDimension()))
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
+
+ // add them together
+ if (addend instanceof BaseUnit)
+ return this.times(2);
+ else
+ return addend.plus(this);
+ }
+
+ @Override
+ public BaseUnit reciprocal() {
+ return this.toExponent(-1);
+ }
+
/**
* Multiplies this unit by another unit.
*
@@ -159,6 +188,22 @@ public final class BaseUnit extends AbstractUnit {
return new LinearUnit(this, multiplier);
}
+ @Override
+ public OperatableUnit times(final OperatableUnit multiplier) {
+ Objects.requireNonNull(multiplier, "multiplier must not be null.");
+
+ // reject multipliers that cannot be muliplied by this unit
+ if (!this.getSystem().equals(multiplier.getSystem()))
+ throw new IllegalArgumentException(String
+ .format("Incompatible units for multiplication or division \"%s\" and \"%s\".", this, multiplier));
+
+ // multiply the units
+ if (multiplier instanceof BaseUnit)
+ return new BaseUnit(this.getDimension().times(multiplier.getDimension()), this.getSystem());
+ else
+ return multiplier.times(this);
+ }
+
/**
* Returns this unit, but to an exponent.
*
@@ -168,6 +213,7 @@ public final class BaseUnit extends AbstractUnit {
* @since 2019-01-15
* @since v0.1.0
*/
+ @Override
public BaseUnit toExponent(final int exponent) {
return this.getSystem().getBaseUnit(this.getDimension().toExponent(exponent));
}
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
index ab46f1e..64eff1f 100644
--- a/src/org/unitConverter/unit/LinearUnit.java
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -27,7 +27,7 @@ import org.unitConverter.dimension.UnitDimension;
* @since 2018-12-22
* @since v0.1.0
*/
-public final class LinearUnit extends AbstractUnit {
+public final class LinearUnit extends AbstractUnit implements OperatableUnit {
/**
* The value of one of this unit in this unit's base unit
*
@@ -134,6 +134,40 @@ public final class LinearUnit extends AbstractUnit {
return result;
}
+ @Override
+ public LinearUnit negated() {
+ return new LinearUnit(this.getBase(), -this.getConversionFactor());
+ }
+
+ @Override
+ public OperatableUnit plus(final OperatableUnit addend) {
+ Objects.requireNonNull(addend, "addend must not be null.");
+
+ // reject addends that cannot be added to this unit
+ if (!this.getSystem().equals(addend.getSystem()))
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
+ if (!this.getDimension().equals(addend.getDimension()))
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
+
+ // add the units
+ if (addend instanceof BaseUnit)
+ // since addend's dimension is equal to this unit's dimension, and there is only one base unit per
+ // system-dimension, addend must be this unit's base.
+ return new LinearUnit(this.getBase(), this.getConversionFactor() + 1);
+ else if (addend instanceof LinearUnit)
+ return new LinearUnit(this.getBase(),
+ this.getConversionFactor() + ((LinearUnit) addend).getConversionFactor());
+ else
+ return addend.times(this);
+ }
+
+ @Override
+ public LinearUnit reciprocal() {
+ return this.toExponent(-1);
+ }
+
/**
* Multiplies this unit by a scalar.
*
@@ -164,6 +198,26 @@ public final class LinearUnit extends AbstractUnit {
return new LinearUnit(base, this.getConversionFactor() * other.getConversionFactor());
}
+ @Override
+ public OperatableUnit times(final OperatableUnit multiplier) {
+ Objects.requireNonNull(multiplier, "multiplier must not be null.");
+
+ // reject multipliers that cannot be muliplied by this unit
+ if (!this.getSystem().equals(multiplier.getSystem()))
+ throw new IllegalArgumentException(String
+ .format("Incompatible units for multiplication or division \"%s\" and \"%s\".", this, multiplier));
+
+ // multiply the units
+ if (multiplier instanceof BaseUnit) {
+ final BaseUnit newBase = this.getBase().times((BaseUnit) multiplier);
+ return new LinearUnit(newBase, this.getConversionFactor());
+ } else if (multiplier instanceof LinearUnit) {
+ final BaseUnit base = this.getBase().times(multiplier.getBase());
+ return new LinearUnit(base, this.getConversionFactor() * ((LinearUnit) multiplier).getConversionFactor());
+ } else
+ return multiplier.times(this);
+ }
+
/**
* Returns this unit but to an exponent.
*
@@ -173,6 +227,7 @@ public final class LinearUnit extends AbstractUnit {
* @since 2019-01-15
* @since v0.1.0
*/
+ @Override
public LinearUnit toExponent(final int exponent) {
return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
}
diff --git a/src/org/unitConverter/unit/OperatableUnit.java b/src/org/unitConverter/unit/OperatableUnit.java
new file mode 100644
index 0000000..ae11c41
--- /dev/null
+++ b/src/org/unitConverter/unit/OperatableUnit.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.unit;
+
+/**
+ * A unit that can be added, subtracted, multiplied or divided by another operatable unit, and raised to an integer
+ * exponent.
+ *
+ * In order to use two units in an operation, they must be part of the same unit system. In addition, in order for two
+ * units to add or subtract, they must measure the same dimension.
+ *
+ *
+ * It is okay for an operation to throw a {@code ClassCastException} if the operator's class cannot operate with another
+ * class. However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
+ * {@code LinearUnit}.
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-03-17
+ */
+public interface OperatableUnit extends Unit {
+ /**
+ * Returns the quotient of this unit and another.
+ *
+ * Two units can be divided if they are part of the same unit system. If {@code divisor} does not meet this
+ * condition, an {@code IllegalArgumentException} should be thrown.
+ *
+ *
+ * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code divisor}'s class.
+ * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
+ * {@code LinearUnit}.
+ *
+ *
+ * @param divisor
+ * unit to divide by
+ * @return quotient
+ * @throws IllegalArgumentException
+ * if {@code divisor} is not compatible for division as described above
+ * @throws NullPointerException
+ * if {@code divisor} is null
+ * @throws ClassCastException
+ * if {@code divisor}'s class is incompatible with this unit's class
+ * @since 2019-03-17
+ */
+ default OperatableUnit dividedBy(final OperatableUnit divisor) {
+ return this.times(divisor.reciprocal());
+ }
+
+ /**
+ * Returns the difference of this unit and another.
+ *
+ * Two units can be subtracted if they meet the following conditions:
+ *
+ *
The two units are part of the same UnitSystem.
+ *
The two units have the same {@code dimension}.
+ *
+ * If {@code subtrahend} does not meet these conditions, an {@code IllegalArgumentException} should be thrown.
+ *
+ *
+ * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code subtrahend}'s class.
+ * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
+ * {@code LinearUnit}.
+ *
+ *
+ * @param subtrahend
+ * unit to subtract
+ * @return difference
+ * @throws IllegalArgumentException
+ * if {@code subtrahend} is not compatible for subtraction as described above
+ * @throws NullPointerException
+ * if {@code subtrahend} is null
+ * @throws ClassCastException
+ * if {@code subtrahend}'s class is incompatible with this unit's class
+ * @since 2019-03-17
+ */
+ default OperatableUnit minus(final OperatableUnit subtrahend) {
+ return this.plus(subtrahend.negated());
+ }
+
+ /**
+ * @return this unit negated, i.e. -this
+ * @since 2019-03-17
+ */
+ OperatableUnit negated();
+
+ /**
+ * Returns the sum of this unit and another.
+ *
+ * Two units can be added if they meet the following conditions:
+ *
+ *
The two units are part of the same UnitSystem.
+ *
The two units have the same {@code dimension}.
+ *
+ * If {@code addend} does not meet these conditions, an {@code IllegalArgumentException} should be thrown.
+ *
+ *
+ * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code addend}'s class.
+ * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
+ * {@code LinearUnit}.
+ *
+ *
+ * @param addend
+ * unit to add
+ * @return sum
+ * @throws IllegalArgumentException
+ * if {@code addend} is not compatible for addition as described above
+ * @throws NullPointerException
+ * if {@code addend} is null
+ * @throws ClassCastException
+ * if {@code addend}'s class is incompatible with this unit's class
+ * @since 2019-03-17
+ */
+ OperatableUnit plus(OperatableUnit addend);
+
+ /**
+ * @return reciprocal of this unit
+ * @since 2019-03-17
+ */
+ OperatableUnit reciprocal();
+
+ /**
+ * Returns the product of this unit and another.
+ *
+ * Two units can be multiplied if they are part of the same unit system. If {@code multiplier} does not meet this
+ * condition, an {@code IllegalArgumentException} should be thrown.
+ *
+ *
+ * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code multiplier}'s class.
+ * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
+ * {@code LinearUnit}.
+ *
+ *
+ * @param multiplier
+ * unit to multiply by
+ * @return product
+ * @throws IllegalArgumentException
+ * if {@code multiplier} is not compatible for multiplication as described above
+ * @throws NullPointerException
+ * if {@code multiplier} is null
+ * @throws ClassCastException
+ * if {@code multiplier}'s class is incompatible with this unit's class
+ * @since 2019-03-17
+ */
+ OperatableUnit times(OperatableUnit multiplier);
+
+ /**
+ * Returns the result of raising this unit to the exponent {@code exponent}.
+ *
+ * @param exponent
+ * exponent to exponentiate by
+ * @return result of exponentiation
+ * @since 2019-03-17
+ */
+ OperatableUnit toExponent(int exponent);
+}
--
cgit v1.2.3
From 943496888d18b031be19ba8e7348ec188dc8eb6b Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Fri, 22 Mar 2019 17:00:58 -0400
Subject: Made BaseUnit a subclass of LinearUnit and made an expression parser
---
CHANGELOG.org | 1 +
src/org/unitConverter/UnitsDatabase.java | 11 +-
.../expressionParser/ExpressionParser.java | 398 -------------
.../expressionParser/package-info.java | 23 -
src/org/unitConverter/math/DecimalComparison.java | 107 ++++
src/org/unitConverter/math/ExpressionParser.java | 627 +++++++++++++++++++++
src/org/unitConverter/math/package-info.java | 23 +
src/org/unitConverter/unit/BaseUnit.java | 151 +----
src/org/unitConverter/unit/LinearUnit.java | 164 ++++--
src/org/unitConverter/unit/OperatableUnit.java | 169 ------
src/test/java/ExpressionParserTest.java | 50 ++
src/test/java/UnitTest.java | 66 +++
12 files changed, 1009 insertions(+), 781 deletions(-)
delete mode 100644 src/org/unitConverter/expressionParser/ExpressionParser.java
delete mode 100644 src/org/unitConverter/expressionParser/package-info.java
create mode 100644 src/org/unitConverter/math/DecimalComparison.java
create mode 100644 src/org/unitConverter/math/ExpressionParser.java
create mode 100644 src/org/unitConverter/math/package-info.java
delete mode 100644 src/org/unitConverter/unit/OperatableUnit.java
create mode 100644 src/test/java/ExpressionParserTest.java
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 87e26e0..5baf980 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -5,6 +5,7 @@ All notable changes in this project will be shown in this file.
*** Changed
- Moved project to Maven
- Downgraded JUnit to 4.11
+ - BaseUnit is now a subclass of LinearUnit
*** Added
- GUI for a selection-based unit converter
- The UnitDatabase now stores dimensions.
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 4d41735..3af1c8d 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -393,8 +393,6 @@ public final class UnitsDatabase {
final Unit unit = this.getUnit(baseAndExponent[0]);
if (unit instanceof LinearUnit) {
base = (LinearUnit) unit;
- } else if (unit instanceof BaseUnit) {
- base = ((BaseUnit) unit).asLinearUnit();
} else
throw new IllegalArgumentException("Base of exponientation must be a linear or base unit.");
}
@@ -464,7 +462,7 @@ public final class UnitsDatabase {
// parse the expression
// start with an "empty" unit then apply operations on it
- LinearUnit unit = SI.SI.getBaseUnit(UnitDimension.EMPTY).asLinearUnit();
+ LinearUnit unit = SI.SI.getBaseUnit(UnitDimension.EMPTY);
boolean dividing = false;
// if I'm just creating an alias, just create one instead of going through the parsing process
@@ -567,8 +565,6 @@ public final class UnitsDatabase {
// try to turn the value into a linear unit
if (valueUnit instanceof LinearUnit) {
value = (LinearUnit) valueUnit;
- } else if (valueUnit instanceof BaseUnit) {
- value = ((BaseUnit) valueUnit).asLinearUnit();
} else
throw new IllegalArgumentException("Only linear and base units can be exponientated.");
}
@@ -594,10 +590,7 @@ public final class UnitsDatabase {
// the unitsfile is looking for a linear unit
if (!this.containsUnitName(part))
throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
- Unit other = this.getUnit(part);
- if (other instanceof BaseUnit) {
- other = ((BaseUnit) other).asLinearUnit();
- }
+ final Unit other = this.getUnit(part);
if (other instanceof LinearUnit) {
if (dividing) {
unit = unit.dividedBy((LinearUnit) other);
diff --git a/src/org/unitConverter/expressionParser/ExpressionParser.java b/src/org/unitConverter/expressionParser/ExpressionParser.java
deleted file mode 100644
index 804ea87..0000000
--- a/src/org/unitConverter/expressionParser/ExpressionParser.java
+++ /dev/null
@@ -1,398 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package org.unitConverter.expressionParser;
-
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.BinaryOperator;
-import java.util.function.Function;
-import java.util.function.UnaryOperator;
-
-/**
- * An object that can parse expressions with unary or binary operators.
- *
- * @author Adrien Hopkins
- * @param
- * type of object that exists in parsed expressions
- * @since 2019-03-14
- */
-// TODO: possibly make this class non-final?
-public final class ExpressionParser {
- /**
- * A builder that can create {@code ExpressionParser} instances.
- *
- * @author Adrien Hopkins
- * @param
- * type of object that exists in parsed expressions
- * @since 2019-03-17
- */
- public static final class Builder {
- /**
- * A function that obtains a parseable object from a string. For example, an integer {@code ExpressionParser}
- * would use {@code Integer::parseInt}.
- *
- * @since 2019-03-14
- */
- private final Function objectObtainer;
-
- /**
- * A map mapping operator strings to operator functions, for unary operators.
- *
- * @since 2019-03-14
- */
- private final Map> unaryOperators;
-
- /**
- * A map mapping operator strings to operator functions, for binary operators.
- *
- * @since 2019-03-14
- */
- private final Map> binaryOperators;
-
- /**
- * Creates the {@code Builder}.
- *
- * @param objectObtainer
- * a function that can turn strings into objects of the type handled by the parser.
- * @throws NullPointerException
- * if {@code objectObtainer} is null
- * @since 2019-03-17
- */
- public Builder(final Function objectObtainer) {
- this.objectObtainer = Objects.requireNonNull(objectObtainer, "objectObtainer must not be null.");
- this.unaryOperators = new HashMap<>();
- this.binaryOperators = new HashMap<>();
- }
-
- /**
- * Adds a binary operator to the builder.
- *
- * @param text
- * text used to reference the operator, like '+'
- * @param operator
- * operator to add
- * @param priority
- * operator's priority, which determines which operators are applied first
- * @return this builder
- * @throws NullPointerException
- * if {@code text} or {@code operator} is null
- * @since 2019-03-17
- */
- public Builder addBinaryOperator(final String text, final BinaryOperator operator, final int priority) {
- Objects.requireNonNull(text, "text must not be null.");
- Objects.requireNonNull(operator, "operator must not be null.");
-
- // Unfortunately, I cannot use a lambda because the PriorityBinaryOperator requires arguments.
- final PriorityBinaryOperator priorityOperator = new PriorityBinaryOperator(priority) {
- @Override
- public T apply(final T t, final T u) {
- return operator.apply(t, u);
- }
-
- };
- this.binaryOperators.put(text, priorityOperator);
- return this;
- }
-
- /**
- * Adds a unary operator to the builder.
- *
- * @param text
- * text used to reference the operator, like '-'
- * @param operator
- * operator to add
- * @param priority
- * operator's priority, which determines which operators are applied first
- * @return this builder
- * @throws NullPointerException
- * if {@code text} or {@code operator} is null
- * @since 2019-03-17
- */
- public Builder addUnaryOperator(final String text, final UnaryOperator operator, final int priority) {
- Objects.requireNonNull(text, "text must not be null.");
- Objects.requireNonNull(operator, "operator must not be null.");
-
- // Unfortunately, I cannot use a lambda because the PriorityUnaryOperator requires arguments.
- final PriorityUnaryOperator priorityOperator = new PriorityUnaryOperator(priority) {
- @Override
- public T apply(final T t) {
- return operator.apply(t);
- }
- };
- this.unaryOperators.put(text, priorityOperator);
- return this;
- }
-
- /**
- * @return an {@code ExpressionParser} instance with the properties given to this builder
- * @since 2019-03-17
- */
- public ExpressionParser build() {
- return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, this.binaryOperators);
- }
- }
-
- /**
- * A binary operator with a priority field that determines which operators apply first.
- *
- * @author Adrien Hopkins
- * @param
- * type of operand and result
- * @since 2019-03-17
- */
- private static abstract class PriorityBinaryOperator
- implements BinaryOperator, Comparable> {
- /**
- * The operator's priority. Higher-priority operators are applied before lower-priority operators
- */
- private final int priority;
-
- /**
- * Creates the {@code PriorityBinaryOperator}.
- *
- * @param priority
- * operator's priority
- * @since 2019-03-17
- */
- public PriorityBinaryOperator(final int priority) {
- this.priority = priority;
- }
-
- /**
- * Compares this object to another by priority.
- */
- @Override
- public int compareTo(final PriorityBinaryOperator o) {
- if (this.priority < o.priority)
- return -1;
- else if (this.priority > o.priority)
- return 1;
- else
- return 0;
- }
- }
-
- /**
- * A unary operator with a priority field that determines which operators apply first.
- *
- * @author Adrien Hopkins
- * @param
- * type of operand and result
- * @since 2019-03-17
- */
- private static abstract class PriorityUnaryOperator
- implements UnaryOperator, Comparable> {
- /**
- * The operator's priority. Higher-priority operators are applied before lower-priority operators
- */
- private final int priority;
-
- /**
- * Creates the {@code PriorityUnaryOperator}.
- *
- * @param priority
- * operator's priority
- * @since 2019-03-17
- */
- public PriorityUnaryOperator(final int priority) {
- this.priority = priority;
- }
-
- /**
- * Compares this object to another by priority.
- */
- @Override
- public int compareTo(final PriorityUnaryOperator o) {
- if (this.priority < o.priority)
- return -1;
- else if (this.priority > o.priority)
- return 1;
- else
- return 0;
- }
- }
-
- /**
- * The types of tokens that are available.
- *
- * @author Adrien Hopkins
- * @since 2019-03-14
- */
- private static enum TokenType {
- OBJECT, UNARY_OPERATOR, BINARY_OPERATOR;
- }
-
- /**
- * A function that obtains a parseable object from a string. For example, an integer {@code ExpressionParser} would
- * use {@code Integer::parseInt}.
- *
- * @since 2019-03-14
- */
- private final Function objectObtainer;
-
- /**
- * A map mapping operator strings to operator functions, for unary operators.
- *
- * @since 2019-03-14
- */
- private final Map> unaryOperators;
-
- /**
- * A map mapping operator strings to operator functions, for binary operators.
- *
- * @since 2019-03-14
- */
- private final Map> binaryOperators;
-
- /**
- * Creates the {@code ExpressionParser}.
- *
- * @param objectObtainer
- * function to get objects from strings
- * @param unaryOperators
- * operators available to the parser
- * @since 2019-03-14
- */
- private ExpressionParser(final Function objectObtainer,
- final Map> unaryOperators,
- final Map> binaryOperators) {
- this.objectObtainer = objectObtainer;
- this.unaryOperators = unaryOperators;
- this.binaryOperators = binaryOperators;
- }
-
- /**
- * Converts a given mathematical expression to reverse Polish notation (operators after operands).
- *
- * For example,
- * {@code 2 * (3 + 4)}
- * becomes
- * {@code 2 3 4 + *}.
- *
- * @param expression
- * @return
- * @since 2019-03-17
- */
- private String convertExpressionToReversePolish(final String expression) {
- Objects.requireNonNull(expression, "expression must not be null.");
-
- // TODO method stub org.unitConverter.expressionParser.ExpressionParser.convertExpressionToPolish(expression)
- throw new UnsupportedOperationException();
- }
-
- /**
- * Determines whether an inputted string is an object or an operator
- *
- * @param token
- * string to input
- * @return type of token it is
- * @throws NullPointerException
- * if {@code expression} is null
- * @since 2019-03-14
- */
- private TokenType getTokenType(final String token) {
- Objects.requireNonNull(token, "token must not be null.");
-
- if (this.unaryOperators.containsKey(token))
- return TokenType.UNARY_OPERATOR;
- else if (this.binaryOperators.containsKey(token))
- return TokenType.BINARY_OPERATOR;
- else
- return TokenType.OBJECT;
- }
-
- /**
- * Parses an expression.
- *
- * @param expression
- * expression to parse
- * @return result
- * @throws NullPointerException
- * if {@code expression} is null
- * @since 2019-03-14
- */
- public T parseExpression(final String expression) {
- return this.parseReversePolishExpression(this.convertExpressionToReversePolish(expression));
- }
-
- /**
- * Parses an expression expressed in reverse Polish notation.
- *
- * @param expression
- * expression to parse
- * @return result
- * @throws NullPointerException
- * if {@code expression} is null
- * @since 2019-03-14
- */
- private T parseReversePolishExpression(final String expression) {
- Objects.requireNonNull(expression, "expression must not be null.");
-
- final Deque stack = new ArrayDeque<>();
-
- // iterate over every item in the expression, then
- for (final String item : expression.split(" ")) {
- // choose a path based on what kind of thing was just read
- switch (this.getTokenType(item)) {
-
- case BINARY_OPERATOR:
- if (stack.size() < 2)
- throw new IllegalStateException(String.format(
- "Attempted to call binary operator %s with only %d arguments.", item, stack.size()));
-
- // get two arguments and operator, then apply!
- final T o1 = stack.pop();
- final T o2 = stack.pop();
- final BinaryOperator binaryOperator = this.binaryOperators.get(item);
-
- stack.push(binaryOperator.apply(o1, o2));
- break;
-
- case OBJECT:
- // just add it to the stack
- stack.push(this.objectObtainer.apply(item));
- break;
-
- case UNARY_OPERATOR:
- if (stack.size() < 1)
- throw new IllegalStateException(String.format(
- "Attempted to call unary operator %s with only %d arguments.", item, stack.size()));
-
- // get one argument and operator, then apply!
- final T o = stack.pop();
- final UnaryOperator unaryOperator = this.unaryOperators.get(item);
-
- stack.push(unaryOperator.apply(o));
- break;
- default:
- throw new AssertionError(
- String.format("Internal error: Invalid token type %s.", this.getTokenType(item)));
-
- }
- }
-
- // return answer, or throw an exception if I can't
- if (stack.size() > 1)
- throw new IllegalStateException("Computation ended up with more than one answer.");
- else if (stack.size() == 0)
- throw new IllegalStateException("Computation ended up without an answer.");
- return stack.pop();
- }
-}
diff --git a/src/org/unitConverter/expressionParser/package-info.java b/src/org/unitConverter/expressionParser/package-info.java
deleted file mode 100644
index 28f0cae..0000000
--- a/src/org/unitConverter/expressionParser/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-/**
- * A module that is capable of parsing expressions of things, like mathematical expressions or unit expressions.
- *
- * @author Adrien Hopkins
- * @since 2019-03-14
- */
-package org.unitConverter.expressionParser;
\ No newline at end of file
diff --git a/src/org/unitConverter/math/DecimalComparison.java b/src/org/unitConverter/math/DecimalComparison.java
new file mode 100644
index 0000000..e6fb733
--- /dev/null
+++ b/src/org/unitConverter/math/DecimalComparison.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.math;
+
+/**
+ * A class that contains methods to compare float and double values.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-03-18
+ */
+public final class DecimalComparison {
+ /**
+ * The value used for double comparison. If two double values are within this value multiplied by the larger value,
+ * they are considered equal.
+ *
+ * @since 2019-03-18
+ */
+ public static final double DOUBLE_EPSILON = 1.0e-15;
+
+ /**
+ * The value used for float comparison. If two float values are within this value multiplied by the larger value,
+ * they are considered equal.
+ *
+ * @since 2019-03-18
+ */
+ public static final float FLOAT_EPSILON = 1.0e-6f;
+
+ /**
+ * Tests for equality of double values using {@link #DOUBLE_EPSILON}.
+ *
+ * @param a
+ * first value to test
+ * @param b
+ * second value to test
+ * @return whether they are equal
+ * @since 2019-03-18
+ */
+ public static final boolean equals(final double a, final double b) {
+ return DecimalComparison.equals(a, b, DOUBLE_EPSILON);
+ }
+
+ /**
+ * Tests for double equality using a custom epsilon value.
+ *
+ * @param a
+ * first value to test
+ * @param b
+ * second value to test
+ * @param epsilon
+ * allowed difference
+ * @return whether they are equal
+ * @since 2019-03-18
+ */
+ public static final boolean equals(final double a, final double b, final double epsilon) {
+ return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
+ }
+
+ /**
+ * Tests for equality of float values using {@link #FLOAT_EPSILON}.
+ *
+ * @param a
+ * first value to test
+ * @param b
+ * second value to test
+ * @return whether they are equal
+ * @since 2019-03-18
+ */
+ public static final boolean equals(final float a, final float b) {
+ return DecimalComparison.equals(a, b, FLOAT_EPSILON);
+ }
+
+ /**
+ * Tests for float equality using a custom epsilon value.
+ *
+ * @param a
+ * first value to test
+ * @param b
+ * second value to test
+ * @param epsilon
+ * allowed difference
+ * @return whether they are equal
+ * @since 2019-03-18
+ */
+ public static final boolean equals(final float a, final float b, final float epsilon) {
+ return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
+ }
+
+ // You may NOT get any DecimalComparison instances
+ private DecimalComparison() {
+ throw new AssertionError();
+ }
+
+}
diff --git a/src/org/unitConverter/math/ExpressionParser.java b/src/org/unitConverter/math/ExpressionParser.java
new file mode 100644
index 0000000..e06a58b
--- /dev/null
+++ b/src/org/unitConverter/math/ExpressionParser.java
@@ -0,0 +1,627 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.math;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+
+/**
+ * An object that can parse expressions with unary or binary operators.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of object that exists in parsed expressions
+ * @since 2019-03-14
+ */
+// TODO: possibly make this class non-final?
+public final class ExpressionParser {
+ /**
+ * A builder that can create {@code ExpressionParser} instances.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of object that exists in parsed expressions
+ * @since 2019-03-17
+ */
+ public static final class Builder {
+ /**
+ * A function that obtains a parseable object from a string. For example, an integer {@code ExpressionParser}
+ * would use {@code Integer::parseInt}.
+ *
+ * @since 2019-03-14
+ */
+ private final Function objectObtainer;
+
+ /**
+ * A map mapping operator strings to operator functions, for unary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> unaryOperators;
+
+ /**
+ * A map mapping operator strings to operator functions, for binary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> binaryOperators;
+
+ /**
+ * Creates the {@code Builder}.
+ *
+ * @param objectObtainer
+ * a function that can turn strings into objects of the type handled by the parser.
+ * @throws NullPointerException
+ * if {@code objectObtainer} is null
+ * @since 2019-03-17
+ */
+ public Builder(final Function objectObtainer) {
+ this.objectObtainer = Objects.requireNonNull(objectObtainer, "objectObtainer must not be null.");
+ this.unaryOperators = new HashMap<>();
+ this.binaryOperators = new HashMap<>();
+ }
+
+ /**
+ * Adds a binary operator to the builder.
+ *
+ * @param text
+ * text used to reference the operator, like '+'
+ * @param operator
+ * operator to add
+ * @param priority
+ * operator's priority, which determines which operators are applied first
+ * @return this builder
+ * @throws NullPointerException
+ * if {@code text} or {@code operator} is null
+ * @since 2019-03-17
+ */
+ public Builder addBinaryOperator(final String text, final BinaryOperator operator, final int priority) {
+ Objects.requireNonNull(text, "text must not be null.");
+ Objects.requireNonNull(operator, "operator must not be null.");
+
+ // Unfortunately, I cannot use a lambda because the PriorityBinaryOperator requires arguments.
+ final PriorityBinaryOperator priorityOperator = new PriorityBinaryOperator(priority) {
+ @Override
+ public T apply(final T t, final T u) {
+ return operator.apply(t, u);
+ }
+
+ };
+ this.binaryOperators.put(text, priorityOperator);
+ return this;
+ }
+
+ /**
+ * Adds a unary operator to the builder.
+ *
+ * @param text
+ * text used to reference the operator, like '-'
+ * @param operator
+ * operator to add
+ * @param priority
+ * operator's priority, which determines which operators are applied first
+ * @return this builder
+ * @throws NullPointerException
+ * if {@code text} or {@code operator} is null
+ * @since 2019-03-17
+ */
+ public Builder addUnaryOperator(final String text, final UnaryOperator operator, final int priority) {
+ Objects.requireNonNull(text, "text must not be null.");
+ Objects.requireNonNull(operator, "operator must not be null.");
+
+ // Unfortunately, I cannot use a lambda because the PriorityUnaryOperator requires arguments.
+ final PriorityUnaryOperator priorityOperator = new PriorityUnaryOperator(priority) {
+ @Override
+ public T apply(final T t) {
+ return operator.apply(t);
+ }
+ };
+ this.unaryOperators.put(text, priorityOperator);
+ return this;
+ }
+
+ /**
+ * @return an {@code ExpressionParser} instance with the properties given to this builder
+ * @since 2019-03-17
+ */
+ public ExpressionParser build() {
+ return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, this.binaryOperators);
+ }
+ }
+
+ /**
+ * A binary operator with a priority field that determines which operators apply first.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of operand and result
+ * @since 2019-03-17
+ */
+ private static abstract class PriorityBinaryOperator
+ implements BinaryOperator, Comparable> {
+ /**
+ * The operator's priority. Higher-priority operators are applied before lower-priority operators
+ */
+ private final int priority;
+
+ /**
+ * Creates the {@code PriorityBinaryOperator}.
+ *
+ * @param priority
+ * operator's priority
+ * @since 2019-03-17
+ */
+ public PriorityBinaryOperator(final int priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * Compares this object to another by priority.
+ *
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public int compareTo(final PriorityBinaryOperator o) {
+ if (this.priority < o.priority)
+ return -1;
+ else if (this.priority > o.priority)
+ return 1;
+ else
+ return 0;
+ }
+
+ /**
+ * @return priority
+ * @since 2019-03-22
+ */
+ public final int getPriority() {
+ return this.priority;
+ }
+ }
+
+ /**
+ * A unary operator with a priority field that determines which operators apply first.
+ *
+ * @author Adrien Hopkins
+ * @param
+ * type of operand and result
+ * @since 2019-03-17
+ */
+ private static abstract class PriorityUnaryOperator
+ implements UnaryOperator, Comparable> {
+ /**
+ * The operator's priority. Higher-priority operators are applied before lower-priority operators
+ */
+ private final int priority;
+
+ /**
+ * Creates the {@code PriorityUnaryOperator}.
+ *
+ * @param priority
+ * operator's priority
+ * @since 2019-03-17
+ */
+ public PriorityUnaryOperator(final int priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * Compares this object to another by priority.
+ *
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public int compareTo(final PriorityUnaryOperator o) {
+ if (this.priority < o.priority)
+ return -1;
+ else if (this.priority > o.priority)
+ return 1;
+ else
+ return 0;
+ }
+
+ /**
+ * @return priority
+ * @since 2019-03-22
+ */
+ public final int getPriority() {
+ return this.priority;
+ }
+ }
+
+ /**
+ * The types of tokens that are available.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-03-14
+ */
+ private static enum TokenType {
+ OBJECT, UNARY_OPERATOR, BINARY_OPERATOR;
+ }
+
+ /**
+ * The opening bracket.
+ *
+ * @since 2019-03-22
+ */
+ public static final char OPENING_BRACKET = '(';
+
+ /**
+ * The closing bracket.
+ *
+ * @since 2019-03-22
+ */
+ public static final char CLOSING_BRACKET = ')';
+
+ /**
+ * Finds the other bracket in a pair of brackets, given the position of one.
+ *
+ * @param string
+ * string that contains brackets
+ * @param bracketPosition
+ * position of first bracket
+ * @return position of matching bracket
+ * @throws NullPointerException
+ * if string is null
+ * @since 2019-03-22
+ */
+ private static int findBracketPair(final String string, final int bracketPosition) {
+ Objects.requireNonNull(string, "string must not be null.");
+
+ final char openingBracket = string.charAt(bracketPosition);
+
+ // figure out what closing bracket to look for
+ final char closingBracket;
+ switch (openingBracket) {
+ case '(':
+ closingBracket = ')';
+ break;
+ case '[':
+ closingBracket = ']';
+ break;
+ case '{':
+ closingBracket = '}';
+ break;
+ default:
+ throw new IllegalArgumentException(String.format("Invalid bracket '%s'", openingBracket));
+ }
+
+ // level of brackets. every opening bracket increments this; every closing bracket decrements it
+ int bracketLevel = 0;
+
+ // iterate over the string to find the closing bracket
+ for (int currentPosition = bracketPosition; currentPosition < string.length(); currentPosition++) {
+ final char currentCharacter = string.charAt(currentPosition);
+
+ if (currentCharacter == openingBracket) {
+ bracketLevel++;
+ } else if (currentCharacter == closingBracket) {
+ bracketLevel--;
+ if (bracketLevel == 0)
+ return currentPosition;
+ }
+ }
+
+ throw new IllegalArgumentException("No matching bracket found.");
+ }
+
+ public static void main(final String[] args) {
+ final ExpressionParser numberParser = new ExpressionParser.Builder<>(Integer::parseInt)
+ .addBinaryOperator("+", (o1, o2) -> o1 + o2, 0).addBinaryOperator("*", (o1, o2) -> o1 * o2, 1)
+ .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build();
+ System.out.println(numberParser.convertExpressionToReversePolish("(1 + 2) ^ 5 * 3"));
+ System.out.println(numberParser.parseExpression("(1 + 2) ^ 5 * 3")); // 729
+ }
+
+ /**
+ * Swaps two elements in a list. Modifies the list passed in instead of returning a modified list.
+ *
+ * @param list
+ * list to swap elements
+ * @param firstIndex
+ * index of first element to swap
+ * @param otherIndex
+ * index of other element to swap
+ * @throws NullPointerException
+ * if list is null
+ * @since 2019-03-20
+ */
+ private static void swap(final List list, final int firstIndex, final int otherIndex) {
+ Objects.requireNonNull(list, "list must not be null.");
+ final E temp = list.get(firstIndex);
+ list.set(firstIndex, list.get(otherIndex));
+ list.set(otherIndex, temp);
+ }
+
+ /**
+ * A function that obtains a parseable object from a string. For example, an integer {@code ExpressionParser} would
+ * use {@code Integer::parseInt}.
+ *
+ * @since 2019-03-14
+ */
+ private final Function objectObtainer;
+
+ /**
+ * A map mapping operator strings to operator functions, for unary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> unaryOperators;
+
+ /**
+ * A map mapping operator strings to operator functions, for binary operators.
+ *
+ * @since 2019-03-14
+ */
+ private final Map> binaryOperators;
+
+ /**
+ * Creates the {@code ExpressionParser}.
+ *
+ * @param objectObtainer
+ * function to get objects from strings
+ * @param unaryOperators
+ * operators available to the parser
+ * @since 2019-03-14
+ */
+ private ExpressionParser(final Function objectObtainer,
+ final Map> unaryOperators,
+ final Map> binaryOperators) {
+ this.objectObtainer = objectObtainer;
+ this.unaryOperators = unaryOperators;
+ this.binaryOperators = binaryOperators;
+ }
+
+ /**
+ * Converts a given mathematical expression to reverse Polish notation (operators after operands).
+ *
+ * For example,
+ * {@code 2 * (3 + 4)}
+ * becomes
+ * {@code 2 3 4 + *}.
+ *
+ * @param expression
+ * expression
+ * @return expression in RPN
+ * @since 2019-03-17
+ */
+ private String convertExpressionToReversePolish(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ final List components = new ArrayList<>();
+
+ // the part of the expression remaining to parse
+ String partialExpression = expression;
+
+ // find and deal with brackets
+ while (partialExpression.indexOf(OPENING_BRACKET) != -1) {
+ final int openingBracketPosition = partialExpression.indexOf(OPENING_BRACKET);
+ final int closingBracketPosition = findBracketPair(partialExpression, openingBracketPosition);
+ components.addAll(Arrays.asList(partialExpression.substring(0, openingBracketPosition).split(" ")));
+ components.add(this.convertExpressionToReversePolish(
+ partialExpression.substring(openingBracketPosition + 1, closingBracketPosition)));
+ partialExpression = partialExpression.substring(closingBracketPosition + 1);
+ }
+
+ // add everything else
+ components.addAll(Arrays.asList(partialExpression.split(" ")));
+
+ // remove empty entries
+ while (components.contains("")) {
+ components.remove("");
+ }
+
+ // turn the expression into reverse Polish
+ while (true) {
+ final int highestPriorityOperatorPosition = this.findHighestPriorityOperatorPosition(components);
+ if (highestPriorityOperatorPosition == -1) {
+ break;
+ }
+
+ switch (this.getTokenType(components.get(highestPriorityOperatorPosition))) {
+ case UNARY_OPERATOR:
+ final String unaryOperator = components.remove(highestPriorityOperatorPosition);
+ final String operand = components.remove(highestPriorityOperatorPosition);
+ components.add(highestPriorityOperatorPosition, operand + " " + unaryOperator);
+ break;
+ case BINARY_OPERATOR:
+ final String binaryOperator = components.remove(highestPriorityOperatorPosition);
+ final String operand1 = components.remove(highestPriorityOperatorPosition - 1);
+ final String operand2 = components.remove(highestPriorityOperatorPosition - 1);
+ components.add(highestPriorityOperatorPosition - 1,
+ operand2 + " " + operand1 + " " + binaryOperator);
+ break;
+ default:
+ throw new AssertionError("Expected operator, found non-operator.");
+ }
+ }
+
+ // join all of the components together, then ensure there is only one space in a row
+ String expressionRPN = String.join(" ", components).replaceAll(" +", " ");
+
+ while (expressionRPN.charAt(0) == ' ') {
+ expressionRPN = expressionRPN.substring(1);
+ }
+ while (expressionRPN.charAt(expressionRPN.length() - 1) == ' ') {
+ expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1);
+ }
+ return expressionRPN;
+
+ // TODO method stub org.unitConverter.expressionParser.ExpressionParser.convertExpressionToPolish(expression)
+ }
+
+ /**
+ * Finds the position of the highest-priority operator in a list
+ *
+ * @param components
+ * components to test
+ * @param blacklist
+ * positions of operators that should be ignored
+ * @return position of highest priority, or -1 if the list contains no operators
+ * @throws NullPointerException
+ * if components is null
+ * @since 2019-03-22
+ */
+ private int findHighestPriorityOperatorPosition(final List components) {
+ Objects.requireNonNull(components, "components must not be null.");
+ // find highest priority
+ int maxPriority = Integer.MIN_VALUE;
+ int maxPriorityPosition = -1;
+
+ // go over components one by one
+ // if it is an operator, test its priority to see if it's max
+ // if it is, update maxPriority and maxPriorityPosition
+ for (int i = 0; i < components.size(); i++) {
+
+ switch (this.getTokenType(components.get(i))) {
+ case UNARY_OPERATOR:
+ final PriorityUnaryOperator unaryOperator = this.unaryOperators.get(components.get(i));
+ final int unaryPriority = unaryOperator.getPriority();
+
+ if (unaryPriority > maxPriority) {
+ maxPriority = unaryPriority;
+ maxPriorityPosition = i;
+ }
+ break;
+ case BINARY_OPERATOR:
+ final PriorityBinaryOperator binaryOperator = this.binaryOperators.get(components.get(i));
+ final int binaryPriority = binaryOperator.getPriority();
+
+ if (binaryPriority > maxPriority) {
+ maxPriority = binaryPriority;
+ maxPriorityPosition = i;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // max priority position found
+ return maxPriorityPosition;
+ }
+
+ /**
+ * Determines whether an inputted string is an object or an operator
+ *
+ * @param token
+ * string to input
+ * @return type of token it is
+ * @throws NullPointerException
+ * if {@code expression} is null
+ * @since 2019-03-14
+ */
+ private TokenType getTokenType(final String token) {
+ Objects.requireNonNull(token, "token must not be null.");
+
+ if (this.unaryOperators.containsKey(token))
+ return TokenType.UNARY_OPERATOR;
+ else if (this.binaryOperators.containsKey(token))
+ return TokenType.BINARY_OPERATOR;
+ else
+ return TokenType.OBJECT;
+ }
+
+ /**
+ * Parses an expression.
+ *
+ * @param expression
+ * expression to parse
+ * @return result
+ * @throws NullPointerException
+ * if {@code expression} is null
+ * @since 2019-03-14
+ */
+ public T parseExpression(final String expression) {
+ return this.parseReversePolishExpression(this.convertExpressionToReversePolish(expression));
+ }
+
+ /**
+ * Parses an expression expressed in reverse Polish notation.
+ *
+ * @param expression
+ * expression to parse
+ * @return result
+ * @throws NullPointerException
+ * if {@code expression} is null
+ * @since 2019-03-14
+ */
+ private T parseReversePolishExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ final Deque stack = new ArrayDeque<>();
+
+ // iterate over every item in the expression, then
+ for (final String item : expression.split(" ")) {
+ // choose a path based on what kind of thing was just read
+ switch (this.getTokenType(item)) {
+
+ case BINARY_OPERATOR:
+ if (stack.size() < 2)
+ throw new IllegalStateException(String.format(
+ "Attempted to call binary operator %s with only %d arguments.", item, stack.size()));
+
+ // get two arguments and operator, then apply!
+ final T o1 = stack.pop();
+ final T o2 = stack.pop();
+ final BinaryOperator binaryOperator = this.binaryOperators.get(item);
+
+ stack.push(binaryOperator.apply(o1, o2));
+ break;
+
+ case OBJECT:
+ // just add it to the stack
+ stack.push(this.objectObtainer.apply(item));
+ break;
+
+ case UNARY_OPERATOR:
+ if (stack.size() < 1)
+ throw new IllegalStateException(String.format(
+ "Attempted to call unary operator %s with only %d arguments.", item, stack.size()));
+
+ // get one argument and operator, then apply!
+ final T o = stack.pop();
+ final UnaryOperator unaryOperator = this.unaryOperators.get(item);
+
+ stack.push(unaryOperator.apply(o));
+ break;
+ default:
+ throw new AssertionError(
+ String.format("Internal error: Invalid token type %s.", this.getTokenType(item)));
+
+ }
+ }
+
+ // return answer, or throw an exception if I can't
+ if (stack.size() > 1)
+ throw new IllegalStateException("Computation ended up with more than one answer.");
+ else if (stack.size() == 0)
+ throw new IllegalStateException("Computation ended up without an answer.");
+ return stack.pop();
+ }
+}
diff --git a/src/org/unitConverter/math/package-info.java b/src/org/unitConverter/math/package-info.java
new file mode 100644
index 0000000..65d6b23
--- /dev/null
+++ b/src/org/unitConverter/math/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * A module that is capable of parsing expressions of things, like mathematical expressions or unit expressions.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-03-14
+ */
+package org.unitConverter.math;
\ No newline at end of file
diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
index 894d338..2def48e 100755
--- a/src/org/unitConverter/unit/BaseUnit.java
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -28,7 +28,7 @@ import org.unitConverter.dimension.UnitDimension;
* @since 2018-12-23
* @since v0.1.0
*/
-public final class BaseUnit extends AbstractUnit implements OperatableUnit {
+public final class BaseUnit extends LinearUnit {
/**
* Is this unit a full base (i.e. m, s, ... but not N, J, ...)
*
@@ -52,156 +52,65 @@ public final class BaseUnit extends AbstractUnit implements OperatableUnit {
* @since v0.1.0
*/
BaseUnit(final UnitDimension dimension, final UnitSystem system) {
- super(dimension, system);
+ super(dimension, system, 1);
this.isFullBase = dimension.isBase();
}
/**
- * @return this unit as a {@code LinearUnit}
- * @since 2019-01-25
- * @since v0.1.0
- */
- public LinearUnit asLinearUnit() {
- return this.times(1);
- }
-
- @Override
- public double convertFromBase(final double value) {
- return value;
- }
-
- @Override
- public double convertToBase(final double value) {
- return value;
- }
-
- /**
- * Divides this unit by another unit.
+ * Returns the quotient of this unit and another.
+ *
+ * Two units can be divided if they are part of the same unit system. If {@code divisor} does not meet this
+ * condition, an {@code IllegalArgumentException} should be thrown.
+ *
*
- * @param other
+ * @param divisor
* unit to divide by
* @return quotient of two units
* @throws IllegalArgumentException
- * if this unit's system is not other's system
+ * if {@code divisor} is not compatible for division as described above
* @throws NullPointerException
- * if other is null
+ * if {@code divisor} is null
* @since 2018-12-22
* @since v0.1.0
*/
- public BaseUnit dividedBy(final BaseUnit other) {
- Objects.requireNonNull(other, "other must not be null.");
- if (!this.getSystem().equals(other.getSystem()))
- throw new IllegalArgumentException("Incompatible base units for division.");
- return new BaseUnit(this.getDimension().dividedBy(other.getDimension()), this.getSystem());
- }
+ public BaseUnit dividedBy(final BaseUnit divisor) {
+ Objects.requireNonNull(divisor, "other must not be null.");
- /**
- * Divides this unit by a divisor
- *
- * @param divisor
- * amount to divide by
- * @return quotient
- * @since 2018-12-23
- * @since v0.1.0
- */
- public LinearUnit dividedBy(final double divisor) {
- return new LinearUnit(this, 1 / divisor);
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (!(obj instanceof BaseUnit))
- return false;
- final BaseUnit other = (BaseUnit) obj;
- return Objects.equals(this.getSystem(), other.getSystem())
- && Objects.equals(this.getDimension(), other.getDimension());
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = result * prime + this.getSystem().hashCode();
- result = result * prime + this.getDimension().hashCode();
- return result;
- }
-
- @Override
- public LinearUnit negated() {
- return this.times(-1);
- }
-
- @Override
- public OperatableUnit plus(final OperatableUnit addend) {
- Objects.requireNonNull(addend, "addend must not be null.");
-
- // reject addends that cannot be added to this unit
- if (!this.getSystem().equals(addend.getSystem()))
- throw new IllegalArgumentException(
- String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
- if (!this.getDimension().equals(addend.getDimension()))
+ // check that these units can be multiplied
+ if (!this.getSystem().equals(divisor.getSystem()))
throw new IllegalArgumentException(
- String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
-
- // add them together
- if (addend instanceof BaseUnit)
- return this.times(2);
- else
- return addend.plus(this);
- }
+ String.format("Incompatible units for division \"%s\" and \"%s\".", this, divisor));
- @Override
- public BaseUnit reciprocal() {
- return this.toExponent(-1);
+ return new BaseUnit(this.getDimension().dividedBy(divisor.getDimension()), this.getSystem());
}
/**
- * Multiplies this unit by another unit.
+ * Returns the product of this unit and another.
+ *
+ * Two units can be multiplied if they are part of the same unit system. If {@code multiplier} does not meet this
+ * condition, an {@code IllegalArgumentException} should be thrown.
+ *
*
- * @param other
+ * @param multiplier
* unit to multiply by
* @return product of two units
* @throws IllegalArgumentException
- * if this unit's system is not other's system
+ * if {@code multiplier} is not compatible for multiplication as described above
* @throws NullPointerException
- * if other is null
+ * if {@code multiplier} is null
* @since 2018-12-22
* @since v0.1.0
*/
- public BaseUnit times(final BaseUnit other) {
- Objects.requireNonNull(other, "other must not be null.");
- if (!this.getSystem().equals(other.getSystem()))
- throw new IllegalArgumentException("Incompatible base units for multiplication.");
- return new BaseUnit(this.getDimension().times(other.getDimension()), this.getSystem());
- }
-
- /**
- * Multiplies this unit by a multiplier.
- *
- * @param multiplier
- * amount to multiply by
- * @return product
- * @since 2018-12-23
- * @since v0.1.0
- */
- public LinearUnit times(final double multiplier) {
- return new LinearUnit(this, multiplier);
- }
+ public BaseUnit times(final BaseUnit multiplier) {
+ Objects.requireNonNull(multiplier, "other must not be null");
- @Override
- public OperatableUnit times(final OperatableUnit multiplier) {
- Objects.requireNonNull(multiplier, "multiplier must not be null.");
-
- // reject multipliers that cannot be muliplied by this unit
+ // check that these units can be multiplied
if (!this.getSystem().equals(multiplier.getSystem()))
- throw new IllegalArgumentException(String
- .format("Incompatible units for multiplication or division \"%s\" and \"%s\".", this, multiplier));
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for multiplication \"%s\" and \"%s\".", this, multiplier));
// multiply the units
- if (multiplier instanceof BaseUnit)
- return new BaseUnit(this.getDimension().times(multiplier.getDimension()), this.getSystem());
- else
- return multiplier.times(this);
+ return new BaseUnit(this.getDimension().times(multiplier.getDimension()), this.getSystem());
}
/**
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
index 64eff1f..c755f79 100644
--- a/src/org/unitConverter/unit/LinearUnit.java
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -19,6 +19,7 @@ package org.unitConverter.unit;
import java.util.Objects;
import org.unitConverter.dimension.UnitDimension;
+import org.unitConverter.math.DecimalComparison;
/**
* A unit that is equal to a certain number multiplied by its base.
@@ -27,7 +28,7 @@ import org.unitConverter.dimension.UnitDimension;
* @since 2018-12-22
* @since v0.1.0
*/
-public final class LinearUnit extends AbstractUnit implements OperatableUnit {
+public class LinearUnit extends AbstractUnit {
/**
* The value of one of this unit in this unit's base unit
*
@@ -91,20 +92,33 @@ public final class LinearUnit extends AbstractUnit implements OperatableUnit {
}
/**
- * Divides this unit by another unit.
+ * Returns the quotient of this unit and another.
+ *
+ * Two units can be divided if they are part of the same unit system. If {@code divisor} does not meet this
+ * condition, an {@code IllegalArgumentException} should be thrown.
+ *
*
- * @param other
+ * @param divisor
* unit to divide by
* @return quotient of two units
+ * @throws IllegalArgumentException
+ * if {@code divisor} is not compatible for division as described above
* @throws NullPointerException
- * if other is null
+ * if {@code divisor} is null
* @since 2018-12-22
* @since v0.1.0
*/
- public LinearUnit dividedBy(final LinearUnit other) {
- Objects.requireNonNull(other, "other must not be null");
- final BaseUnit base = this.getBase().dividedBy(other.getBase());
- return new LinearUnit(base, this.getConversionFactor() / other.getConversionFactor());
+ public LinearUnit dividedBy(final LinearUnit divisor) {
+ Objects.requireNonNull(divisor, "other must not be null");
+
+ // check that these units can be multiplied
+ if (!this.getSystem().equals(divisor.getSystem()))
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for division \"%s\" and \"%s\".", this, divisor));
+
+ // divide the units
+ final BaseUnit base = this.getBase().dividedBy(divisor.getBase());
+ return new LinearUnit(base, this.getConversionFactor() / divisor.getConversionFactor());
}
@Override
@@ -112,12 +126,13 @@ public final class LinearUnit extends AbstractUnit implements OperatableUnit {
if (!(obj instanceof LinearUnit))
return false;
final LinearUnit other = (LinearUnit) obj;
- return Objects.equals(this.getBase(), other.getBase())
- && Objects.equals(this.getConversionFactor(), other.getConversionFactor());
+ return Objects.equals(this.getSystem(), other.getSystem())
+ && Objects.equals(this.getDimension(), other.getDimension())
+ && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor());
}
/**
- * @return conversionFactor
+ * @return conversion factor between this unit and its base
* @since 2018-12-22
* @since v0.1.0
*/
@@ -129,43 +144,66 @@ public final class LinearUnit extends AbstractUnit implements OperatableUnit {
public int hashCode() {
final int prime = 31;
int result = 1;
- result = result * prime + this.getBase().hashCode();
+ result = result * prime + this.getSystem().hashCode();
+ result = result * prime + this.getDimension().hashCode();
result = result * prime + Double.hashCode(this.getConversionFactor());
return result;
}
- @Override
- public LinearUnit negated() {
- return new LinearUnit(this.getBase(), -this.getConversionFactor());
+ /**
+ * Returns the difference of this unit and another.
+ *
+ * Two units can be subtracted if they have the same base. If {@code subtrahend} does not meet this condition, an
+ * {@code IllegalArgumentException} will be thrown.
+ *
+ *
+ * @param subtrahend
+ * unit to subtract
+ * @return difference of units
+ * @throws IllegalArgumentException
+ * if {@code subtrahend} is not compatible for subtraction as described above
+ * @throws NullPointerException
+ * if {@code subtrahend} is null
+ * @since 2019-03-17
+ */
+ public LinearUnit minus(final LinearUnit subtrahendend) {
+ Objects.requireNonNull(subtrahendend, "addend must not be null.");
+
+ // reject subtrahends that cannot be added to this unit
+ if (!this.getBase().equals(subtrahendend.getBase()))
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahendend));
+
+ // add the units
+ return new LinearUnit(this.getBase(), this.getConversionFactor() - subtrahendend.getConversionFactor());
}
- @Override
- public OperatableUnit plus(final OperatableUnit addend) {
+ /**
+ * Returns the sum of this unit and another.
+ *
+ * Two units can be added if they have the same base. If {@code addend} does not meet this condition, an
+ * {@code IllegalArgumentException} will be thrown.
+ *
+ *
+ * @param addend
+ * unit to add
+ * @return sum of units
+ * @throws IllegalArgumentException
+ * if {@code addend} is not compatible for addition as described above
+ * @throws NullPointerException
+ * if {@code addend} is null
+ * @since 2019-03-17
+ */
+ public LinearUnit plus(final LinearUnit addend) {
Objects.requireNonNull(addend, "addend must not be null.");
// reject addends that cannot be added to this unit
- if (!this.getSystem().equals(addend.getSystem()))
- throw new IllegalArgumentException(
- String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
- if (!this.getDimension().equals(addend.getDimension()))
+ if (!this.getBase().equals(addend.getBase()))
throw new IllegalArgumentException(
- String.format("Incompatible units for addition or subtraction \"%s\" and \"%s\".", this, addend));
+ String.format("Incompatible units for addition \"%s\" and \"%s\".", this, addend));
// add the units
- if (addend instanceof BaseUnit)
- // since addend's dimension is equal to this unit's dimension, and there is only one base unit per
- // system-dimension, addend must be this unit's base.
- return new LinearUnit(this.getBase(), this.getConversionFactor() + 1);
- else if (addend instanceof LinearUnit)
- return new LinearUnit(this.getBase(),
- this.getConversionFactor() + ((LinearUnit) addend).getConversionFactor());
- else
- return addend.times(this);
- }
-
- @Override
- public LinearUnit reciprocal() {
- return this.toExponent(-1);
+ return new LinearUnit(this.getBase(), this.getConversionFactor() + addend.getConversionFactor());
}
/**
@@ -182,40 +220,33 @@ public final class LinearUnit extends AbstractUnit implements OperatableUnit {
}
/**
- * Multiplies this unit by another unit.
+ * Returns the product of this unit and another.
+ *
+ * Two units can be multiplied if they are part of the same unit system. If {@code multiplier} does not meet this
+ * condition, an {@code IllegalArgumentException} should be thrown.
+ *
*
- * @param other
- * unit to multiply by=
+ * @param multiplier
+ * unit to multiply by
* @return product of two units
+ * @throws IllegalArgumentException
+ * if {@code multiplier} is not compatible for multiplication as described above
* @throws NullPointerException
- * if other is null
+ * if {@code multiplier} is null
* @since 2018-12-22
* @since v0.1.0
*/
- public LinearUnit times(final LinearUnit other) {
- Objects.requireNonNull(other, "other must not be null");
- final BaseUnit base = this.getBase().times(other.getBase());
- return new LinearUnit(base, this.getConversionFactor() * other.getConversionFactor());
- }
-
- @Override
- public OperatableUnit times(final OperatableUnit multiplier) {
- Objects.requireNonNull(multiplier, "multiplier must not be null.");
+ public LinearUnit times(final LinearUnit multiplier) {
+ Objects.requireNonNull(multiplier, "other must not be null");
- // reject multipliers that cannot be muliplied by this unit
+ // check that these units can be multiplied
if (!this.getSystem().equals(multiplier.getSystem()))
- throw new IllegalArgumentException(String
- .format("Incompatible units for multiplication or division \"%s\" and \"%s\".", this, multiplier));
+ throw new IllegalArgumentException(
+ String.format("Incompatible units for multiplication \"%s\" and \"%s\".", this, multiplier));
// multiply the units
- if (multiplier instanceof BaseUnit) {
- final BaseUnit newBase = this.getBase().times((BaseUnit) multiplier);
- return new LinearUnit(newBase, this.getConversionFactor());
- } else if (multiplier instanceof LinearUnit) {
- final BaseUnit base = this.getBase().times(multiplier.getBase());
- return new LinearUnit(base, this.getConversionFactor() * ((LinearUnit) multiplier).getConversionFactor());
- } else
- return multiplier.times(this);
+ final BaseUnit base = this.getBase().times(multiplier.getBase());
+ return new LinearUnit(base, this.getConversionFactor() * multiplier.getConversionFactor());
}
/**
@@ -227,7 +258,6 @@ public final class LinearUnit extends AbstractUnit implements OperatableUnit {
* @since 2019-01-15
* @since v0.1.0
*/
- @Override
public LinearUnit toExponent(final int exponent) {
return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
}
@@ -236,4 +266,16 @@ public final class LinearUnit extends AbstractUnit implements OperatableUnit {
public String toString() {
return super.toString() + String.format(" (equal to %s * base)", this.getConversionFactor());
}
+
+ /**
+ * Returns the result of applying {@code prefix} to this unit.
+ *
+ * @param prefix
+ * prefix to apply
+ * @return unit with prefix
+ * @since 2019-03-18
+ */
+ public LinearUnit withPrefix(final UnitPrefix prefix) {
+ return this.times(prefix.getMultiplier());
+ }
}
diff --git a/src/org/unitConverter/unit/OperatableUnit.java b/src/org/unitConverter/unit/OperatableUnit.java
deleted file mode 100644
index ae11c41..0000000
--- a/src/org/unitConverter/unit/OperatableUnit.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/**
- * Copyright (C) 2019 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-package org.unitConverter.unit;
-
-/**
- * A unit that can be added, subtracted, multiplied or divided by another operatable unit, and raised to an integer
- * exponent.
- *
- * In order to use two units in an operation, they must be part of the same unit system. In addition, in order for two
- * units to add or subtract, they must measure the same dimension.
- *
- *
- * It is okay for an operation to throw a {@code ClassCastException} if the operator's class cannot operate with another
- * class. However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
- * {@code LinearUnit}.
- *
- *
- * @author Adrien Hopkins
- * @since 2019-03-17
- */
-public interface OperatableUnit extends Unit {
- /**
- * Returns the quotient of this unit and another.
- *
- * Two units can be divided if they are part of the same unit system. If {@code divisor} does not meet this
- * condition, an {@code IllegalArgumentException} should be thrown.
- *
- *
- * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code divisor}'s class.
- * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
- * {@code LinearUnit}.
- *
- *
- * @param divisor
- * unit to divide by
- * @return quotient
- * @throws IllegalArgumentException
- * if {@code divisor} is not compatible for division as described above
- * @throws NullPointerException
- * if {@code divisor} is null
- * @throws ClassCastException
- * if {@code divisor}'s class is incompatible with this unit's class
- * @since 2019-03-17
- */
- default OperatableUnit dividedBy(final OperatableUnit divisor) {
- return this.times(divisor.reciprocal());
- }
-
- /**
- * Returns the difference of this unit and another.
- *
- * Two units can be subtracted if they meet the following conditions:
- *
- *
The two units are part of the same UnitSystem.
- *
The two units have the same {@code dimension}.
- *
- * If {@code subtrahend} does not meet these conditions, an {@code IllegalArgumentException} should be thrown.
- *
- *
- * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code subtrahend}'s class.
- * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
- * {@code LinearUnit}.
- *
- *
- * @param subtrahend
- * unit to subtract
- * @return difference
- * @throws IllegalArgumentException
- * if {@code subtrahend} is not compatible for subtraction as described above
- * @throws NullPointerException
- * if {@code subtrahend} is null
- * @throws ClassCastException
- * if {@code subtrahend}'s class is incompatible with this unit's class
- * @since 2019-03-17
- */
- default OperatableUnit minus(final OperatableUnit subtrahend) {
- return this.plus(subtrahend.negated());
- }
-
- /**
- * @return this unit negated, i.e. -this
- * @since 2019-03-17
- */
- OperatableUnit negated();
-
- /**
- * Returns the sum of this unit and another.
- *
- * Two units can be added if they meet the following conditions:
- *
- *
The two units are part of the same UnitSystem.
- *
The two units have the same {@code dimension}.
- *
- * If {@code addend} does not meet these conditions, an {@code IllegalArgumentException} should be thrown.
- *
- *
- * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code addend}'s class.
- * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
- * {@code LinearUnit}.
- *
- *
- * @param addend
- * unit to add
- * @return sum
- * @throws IllegalArgumentException
- * if {@code addend} is not compatible for addition as described above
- * @throws NullPointerException
- * if {@code addend} is null
- * @throws ClassCastException
- * if {@code addend}'s class is incompatible with this unit's class
- * @since 2019-03-17
- */
- OperatableUnit plus(OperatableUnit addend);
-
- /**
- * @return reciprocal of this unit
- * @since 2019-03-17
- */
- OperatableUnit reciprocal();
-
- /**
- * Returns the product of this unit and another.
- *
- * Two units can be multiplied if they are part of the same unit system. If {@code multiplier} does not meet this
- * condition, an {@code IllegalArgumentException} should be thrown.
- *
- *
- * It is okay for a unit to throw a {@code ClassCastException} if it cannot operate with {@code multiplier}'s class.
- * However, all classes that implement this interface should be able to interoperate with {@code BaseUnit} and
- * {@code LinearUnit}.
- *
- *
- * @param multiplier
- * unit to multiply by
- * @return product
- * @throws IllegalArgumentException
- * if {@code multiplier} is not compatible for multiplication as described above
- * @throws NullPointerException
- * if {@code multiplier} is null
- * @throws ClassCastException
- * if {@code multiplier}'s class is incompatible with this unit's class
- * @since 2019-03-17
- */
- OperatableUnit times(OperatableUnit multiplier);
-
- /**
- * Returns the result of raising this unit to the exponent {@code exponent}.
- *
- * @param exponent
- * exponent to exponentiate by
- * @return result of exponentiation
- * @since 2019-03-17
- */
- OperatableUnit toExponent(int exponent);
-}
diff --git a/src/test/java/ExpressionParserTest.java b/src/test/java/ExpressionParserTest.java
new file mode 100644
index 0000000..e81ca40
--- /dev/null
+++ b/src/test/java/ExpressionParserTest.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package test.java;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.unitConverter.math.ExpressionParser;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2019-03-22
+ */
+public class ExpressionParserTest {
+ private static final ExpressionParser numberParser = new ExpressionParser.Builder<>(Integer::parseInt)
+ .addBinaryOperator("+", (o1, o2) -> o1 + o2, 0).addBinaryOperator("-", (o1, o2) -> o1 - o2, 0)
+ .addBinaryOperator("*", (o1, o2) -> o1 * o2, 1).addBinaryOperator("/", (o1, o2) -> o1 / o2, 1)
+ .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build();
+
+ /**
+ * Test method for {@link org.unitConverter.math.ExpressionParser#parseExpression(java.lang.String)}.
+ */
+ @Test
+ public void testParseExpression() {
+ // test parsing of expressions
+ assertEquals((int) numberParser.parseExpression("1 + 2 ^ 5 * 3"), 97);
+ assertEquals((int) numberParser.parseExpression("(1 + 2) ^ 5 * 3"), 729);
+
+ // ensure it normally goes left to right
+ assertEquals((int) numberParser.parseExpression("1 + 2 + 3 + 4"), 10);
+ assertEquals((int) numberParser.parseExpression("12 - 4 - 3"), 5);
+ assertEquals((int) numberParser.parseExpression("12 - (4 - 3)"), 11);
+ assertEquals((int) numberParser.parseExpression("1 / 2 + 3"), 3);
+ }
+
+}
diff --git a/src/test/java/UnitTest.java b/src/test/java/UnitTest.java
index 45f890f..79bc3d1 100755
--- a/src/test/java/UnitTest.java
+++ b/src/test/java/UnitTest.java
@@ -18,10 +18,16 @@ package test.java;
import static org.junit.Assert.assertEquals;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
import org.junit.Test;
import org.unitConverter.dimension.StandardDimensions;
+import org.unitConverter.math.DecimalComparison;
import org.unitConverter.unit.BaseUnit;
+import org.unitConverter.unit.LinearUnit;
import org.unitConverter.unit.SI;
+import org.unitConverter.unit.SIPrefix;
import org.unitConverter.unit.Unit;
/**
@@ -31,12 +37,46 @@ import org.unitConverter.unit.Unit;
* @since 2018-12-22
*/
public class UnitTest {
+ /** A random number generator */
+ private static final Random rng = ThreadLocalRandom.current();
+
+ @Test
+ public void testAdditionAndSubtraction() {
+ final LinearUnit inch = SI.METRE.times(0.0254);
+ final LinearUnit foot = SI.METRE.times(0.3048);
+
+ assertEquals(inch.plus(foot), SI.METRE.times(0.3302));
+ assertEquals(foot.minus(inch), SI.METRE.times(0.2794));
+ }
+
+ @Test
+ public void testBaseUnitExclusives() {
+ // this test should have a compile error if I am doing something wrong
+ final BaseUnit metrePerSecondSquared = SI.METRE.dividedBy(SI.SECOND.toExponent(2));
+
+ assertEquals(metrePerSecondSquared, SI.SI.getBaseUnit(StandardDimensions.ACCELERATION));
+ }
+
@Test
public void testConversion() {
final BaseUnit metre = SI.METRE;
final Unit inch = metre.times(0.0254);
assertEquals(1.9, inch.convertToBase(75), 0.01);
+
+ // try random stuff
+ for (int i = 0; i < 1000; i++) {
+ // initiate random values
+ final double conversionFactor = rng.nextDouble() * 1000000;
+ final double testValue = rng.nextDouble() * 1000000;
+ final double expected = testValue * conversionFactor;
+
+ // test
+ final Unit unit = SI.METRE.times(conversionFactor);
+ final double actual = unit.convertToBase(testValue);
+
+ assertEquals(actual, expected, expected * DecimalComparison.DOUBLE_EPSILON);
+ }
}
@Test
@@ -46,4 +86,30 @@ public class UnitTest {
assertEquals(metre, meter);
}
+
+ @Test
+ public void testMultiplicationAndDivision() {
+ // test unit-times-unit multiplication
+ final LinearUnit generatedJoule = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
+ final LinearUnit actualJoule = SI.SI.getBaseUnit(StandardDimensions.ENERGY);
+
+ assertEquals(generatedJoule, actualJoule);
+
+ // test multiplication by conversion factors
+ final LinearUnit kilometre = SI.METRE.times(1000);
+ final LinearUnit hour = SI.SECOND.times(3600);
+ final LinearUnit generatedKPH = kilometre.dividedBy(hour);
+
+ final LinearUnit actualKPH = SI.SI.getBaseUnit(StandardDimensions.VELOCITY).dividedBy(3.6);
+
+ assertEquals(generatedKPH, actualKPH);
+ }
+
+ @Test
+ public void testPrefixes() {
+ final LinearUnit generatedKilometre = SI.METRE.withPrefix(SIPrefix.KILO);
+ final LinearUnit actualKilometre = SI.METRE.times(1000);
+
+ assertEquals(generatedKilometre, actualKilometre);
+ }
}
--
cgit v1.2.3
From bfe1f266922bffd3c0c8d8906535be7621217e7a Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Fri, 22 Mar 2019 19:35:30 -0400
Subject: Unit Expressions are now parsed with the expression parser.
Addition and subtraction are now possible.
---
CHANGELOG.org | 1 +
src/org/unitConverter/UnitsDatabase.java | 326 +++++++++------------
.../converterGUI/UnitConverterGUI.java | 7 +-
src/org/unitConverter/math/ExpressionParser.java | 108 ++++---
unitsfile.txt | 2 +-
5 files changed, 213 insertions(+), 231 deletions(-)
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 5baf980..95dc57a 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -10,6 +10,7 @@ All notable changes in this project will be shown in this file.
- 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!
** v0.1.0
NOTE: At this stage, the API is subject to significant change.
*** Added
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 3af1c8d..481ce93 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -21,13 +21,17 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.unitConverter.dimension.UnitDimension;
+import org.unitConverter.math.DecimalComparison;
+import org.unitConverter.math.ExpressionParser;
import org.unitConverter.unit.AbstractUnit;
import org.unitConverter.unit.BaseUnit;
import org.unitConverter.unit.DefaultUnitPrefix;
@@ -67,6 +71,32 @@ public final class UnitsDatabase {
*/
private final Map dimensions;
+ /**
+ * A parser that can parse unit expressions.
+ *
+ * @since 2019-03-22
+ */
+ private final ExpressionParser 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.dividedBy(o2), 1).addBinaryOperator("^", (o1, o2) -> {
+ // exponent function - first check if o2 is a number,
+ if (o2.getBase().equals(SI.SI.getBaseUnit(UnitDimension.EMPTY))) {
+ // then check if it is an integer,
+ final double exponent = o2.getConversionFactor();
+ if (DecimalComparison.equals(exponent % 1, 0))
+ // then exponentiate
+ return o1.toExponent((int) (exponent % 1 + 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.");
+ }, 2).build();
+
/**
* Creates the {@code UnitsDatabase}.
*
@@ -298,6 +328,37 @@ public final class UnitsDatabase {
return this.dimensions.get(name);
}
+ /**
+ * 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
+ * @return unit
+ * @since 2019-03-22
+ */
+ private LinearUnit getLinearUnit(final String name) {
+ // see if I am using a function-unit like tempC(100)
+ if (name.contains("(") && name.contains(")")) {
+ // break it into function name and value
+ final List parts = Arrays.asList(name.split("\\("));
+ if (parts.size() != 2)
+ 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));
+ return unit.getBase().times(unit.convertToBase(value));
+ } else {
+ // get a linear unit
+ final Unit unit = this.getUnit(name);
+ if (unit instanceof LinearUnit)
+ return (LinearUnit) unit;
+ else
+ throw new IllegalArgumentException(String.format("%s is not a linear unit.", name));
+ }
+ }
+
/**
* Gets a unit prefix from the database from its name
*
@@ -383,55 +444,60 @@ public final class UnitsDatabase {
* @since v0.1.0
*/
public Unit getUnit(final String name) {
- if (name.contains("^")) {
- final String[] baseAndExponent = name.split("\\^");
-
- LinearUnit base;
- try {
- base = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(baseAndExponent[0]));
- } catch (final NumberFormatException e2) {
- final Unit unit = this.getUnit(baseAndExponent[0]);
- if (unit instanceof LinearUnit) {
- base = (LinearUnit) unit;
- } else
- throw new IllegalArgumentException("Base of exponientation must be a linear or base unit.");
- }
+ try {
+ final double value = Double.parseDouble(name);
+ return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(value);
+ } catch (final NumberFormatException e) {
+ if (name.contains("^")) {
+ final String[] baseAndExponent = name.split("\\^");
- final int exponent;
- try {
- exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponent must be an integer.");
- }
+ LinearUnit base;
+ try {
+ base = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(baseAndExponent[0]));
+ } catch (final NumberFormatException e2) {
+ final Unit unit = this.getUnit(baseAndExponent[0]);
+ if (unit instanceof LinearUnit) {
+ base = (LinearUnit) unit;
+ } else
+ throw new IllegalArgumentException("Base of exponientation must be a linear or base unit.");
+ }
- final LinearUnit exponentiated = base.toExponent(exponent);
- if (exponentiated.getConversionFactor() == 1)
- return exponentiated.getSystem().getBaseUnit(exponentiated.getDimension());
- else
- return exponentiated;
- } else {
- for (final String prefixName : this.prefixNameSet()) {
- // check for a prefix
- if (name.startsWith(prefixName)) {
- // prefix found! Make sure what comes after it is actually a unit!
- final String prefixless = name.substring(prefixName.length());
- if (this.containsUnitName(prefixless)) {
- // yep, it's a proper prefix! Get the unit!
- final Unit unit = this.getUnit(prefixless);
- final UnitPrefix prefix = this.getPrefix(prefixName);
-
- // Prefixes only work with linear and base units, so make sure it's one of those
- if (unit instanceof LinearUnit) {
- final LinearUnit linearUnit = (LinearUnit) unit;
- return linearUnit.times(prefix.getMultiplier());
- } else if (unit instanceof BaseUnit) {
- final BaseUnit baseUnit = (BaseUnit) unit;
- return baseUnit.times(prefix.getMultiplier());
+ final int exponent;
+ try {
+ exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponent must be an integer.");
+ }
+
+ final LinearUnit exponentiated = base.toExponent(exponent);
+ if (exponentiated.getConversionFactor() == 1)
+ return exponentiated.getSystem().getBaseUnit(exponentiated.getDimension());
+ else
+ return exponentiated;
+ } else {
+ for (final String prefixName : this.prefixNameSet()) {
+ // check for a prefix
+ if (name.startsWith(prefixName)) {
+ // prefix found! Make sure what comes after it is actually a unit!
+ final String prefixless = name.substring(prefixName.length());
+ if (this.containsUnitName(prefixless)) {
+ // yep, it's a proper prefix! Get the unit!
+ final Unit unit = this.getUnit(prefixless);
+ final UnitPrefix prefix = this.getPrefix(prefixName);
+
+ // Prefixes only work with linear and base units, so make sure it's one of those
+ if (unit instanceof LinearUnit) {
+ final LinearUnit linearUnit = (LinearUnit) unit;
+ return linearUnit.times(prefix.getMultiplier());
+ } else if (unit instanceof BaseUnit) {
+ final BaseUnit baseUnit = (BaseUnit) unit;
+ return baseUnit.times(prefix.getMultiplier());
+ }
}
}
}
+ return this.units.get(name);
}
- return this.units.get(name);
}
}
@@ -453,165 +519,39 @@ public final class UnitsDatabase {
* @throws IllegalArgumentException
* if the expression cannot be parsed
* @throws NullPointerException
- * if any argument is null
+ * 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.");
- // parse the expression
- // start with an "empty" unit then apply operations on it
- LinearUnit unit = SI.SI.getBaseUnit(UnitDimension.EMPTY);
- boolean dividing = false;
-
- // if I'm just creating an alias, just create one instead of going through the parsing process
- if (!expression.contains(" ") && !expression.contains("*") && !expression.contains("/")
- && !expression.contains("(") && !expression.contains(")") && !expression.contains("^")) {
- try {
- return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(expression));
- } catch (final NumberFormatException e) {
- if (!this.containsUnitName(expression))
- throw new IllegalArgumentException("Unrecognized unit name \"" + expression + "\".");
- return this.getUnit(expression);
- }
- }
-
- // \\* means "asterisk", * is reserved
- for (final String part : expression.replaceAll("\\*", " \\* ").replaceAll("/", " / ").replaceAll(" \\^", "\\^")
- .replaceAll("\\^ ", "\\^").split(" ")) {
- if ("".equals(part)) {
- continue;
- }
- // "unit1 unit2" is the same as "unit1 * unit2", so multiplication signs do nothing
- if ("*".equals(part)) {
- continue;
- }
- // When I reach a division sign, don't parse a unit, instead tell myself I'm going to divide the next
- // thing
- if ("/".equals(part) || "per".equals(part)) {
- dividing = true;
- continue;
- }
-
- try {
- final double partAsNumber = Double.parseDouble(part); // if this works, it's a number
- // this code should not throw any exceptions, so I'm going to throw AssertionErrors if it does
- try {
- if (dividing) {
- unit = unit.dividedBy(partAsNumber);
- dividing = false;
- } else {
- unit = unit.times(partAsNumber);
- }
- } catch (final Exception e) {
- throw new AssertionError(e);
- }
- } catch (final NumberFormatException e) {
- // it's a unit, try that
-
- if (part.contains("(") && part.endsWith(")")) {
- // the unitsfile is looking for a nonlinear unit
- final String[] unitAndValue = part.split("\\(");
-
- // this will work because I've checked that it contains a (
- final String unitName = unitAndValue[0];
- final String valueStringWithBracket = unitAndValue[unitAndValue.length - 1];
- final String valueString = valueStringWithBracket.substring(0, valueStringWithBracket.length() - 1);
- final double value;
-
- // try to get the value - else throw an error
- try {
- value = Double.parseDouble(valueString);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Unparseable value " + valueString);
- }
-
- // get this unit in a linear form
- if (!this.containsPrefixlessUnitName(unitName))
- throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
- final Unit partUnit = this.getPrefixlessUnit(unitName);
- final LinearUnit multiplier = partUnit.getBase().times(partUnit.convertToBase(value));
-
- // finally, add it to the expression
- if (dividing) {
- unit = unit.dividedBy(multiplier);
- dividing = false;
- } else {
- unit = unit.times(multiplier);
- }
- } else {
- // check for exponientation
- if (part.contains("^")) {
- final String[] valueAndExponent = part.split("\\^");
- // this will always work because of the contains check
- final String valueString = valueAndExponent[0];
- final String exponentString = valueAndExponent[valueAndExponent.length - 1];
-
- LinearUnit value;
-
- // first, try to get the value
- try {
- final double valueAsNumber = Double.parseDouble(valueString);
-
- value = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(valueAsNumber);
- } catch (final NumberFormatException e2) {
-
- // look for a unit
- if (!this.containsUnitName(valueString))
- throw new IllegalArgumentException("Unrecognized unit name \"" + valueString + "\".");
- final Unit valueUnit = this.getUnit(valueString);
-
- // try to turn the value into a linear unit
- if (valueUnit instanceof LinearUnit) {
- value = (LinearUnit) valueUnit;
- } else
- throw new IllegalArgumentException("Only linear and base units can be exponientated.");
- }
-
- // now, try to get the exponent
- final int exponent;
- try {
- exponent = Integer.parseInt(exponentString);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponents must be integers.");
- }
-
- final LinearUnit exponientated = value.toExponent(exponent);
-
- if (dividing) {
- unit = unit.dividedBy(exponientated);
- dividing = false;
- } else {
- unit = unit.times(exponientated);
- }
- } else {
- // no exponent - look for a unit
- // the unitsfile is looking for a linear unit
- if (!this.containsUnitName(part))
- throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
- final Unit other = this.getUnit(part);
- if (other instanceof LinearUnit) {
- if (dividing) {
- unit = unit.dividedBy((LinearUnit) other);
- dividing = false;
- } else {
- unit = unit.times((LinearUnit) other);
- }
- } else
- throw new IllegalArgumentException(
- "Only linear or base units can be multiplied and divided. If you want to use a non-linear unit, try the format UNITNAME(VALUE).");
- }
- }
+ // 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(" +", " ");
+
+ // the previous operation breaks negative numbers, fix them!
+ // (i.e. -2 becomes - 2)
+ for (int i = 2; i < modifiedExpression.length(); i++) {
+ if (modifiedExpression.charAt(i) == '-'
+ && Arrays.asList('+', '-', '*', '/', '^').contains(modifiedExpression.charAt(i - 2))) {
+ // found a broken negative number
+ modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2);
}
}
- // replace conversion-factor-1 linear units with base units
- // this improves the autogenerated names, allowing them to use derived SI names
- if (unit != null && unit.getConversionFactor() == 1)
- return unit.getSystem().getBaseUnit(unit.getDimension());
- else
- return unit;
+ return this.unitExpressionParser.parseExpression(modifiedExpression);
}
/**
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index a70e971..867211c 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -175,7 +175,12 @@ final class UnitConverterGUI {
// try to parse to
final Unit to;
try {
- to = this.units.getUnitFromExpression(toUnitString);
+ // if it's a unit, convert to that
+ if (this.units.containsUnitName(toUnitString)) {
+ to = this.units.getUnit(toUnitString);
+ } else {
+ to = this.units.getUnitFromExpression(toUnitString);
+ }
} catch (final IllegalArgumentException e) {
this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage());
return;
diff --git a/src/org/unitConverter/math/ExpressionParser.java b/src/org/unitConverter/math/ExpressionParser.java
index e06a58b..f34a0c2 100644
--- a/src/org/unitConverter/math/ExpressionParser.java
+++ b/src/org/unitConverter/math/ExpressionParser.java
@@ -55,6 +55,13 @@ public final class ExpressionParser {
*/
private final Function objectObtainer;
+ /**
+ * The function of the space as an operator (like 3 x y)
+ *
+ * @since 2019-03-22
+ */
+ private String spaceFunction = null;
+
/**
* A map mapping operator strings to operator functions, for unary operators.
*
@@ -114,6 +121,24 @@ public final class ExpressionParser {
return this;
}
+ /**
+ * Adds a function for spaces. You must use the text of an existing binary operator.
+ *
+ * @param operator
+ * text of operator to use
+ * @return this builder
+ * @since 2019-03-22
+ */
+ public Builder addSpaceFunction(final String operator) {
+ Objects.requireNonNull(operator, "operator must not be null.");
+
+ if (!this.binaryOperators.containsKey(operator))
+ throw new IllegalArgumentException(String.format("Could not find binary operator '%s'", operator));
+
+ this.spaceFunction = operator;
+ return this;
+ }
+
/**
* Adds a unary operator to the builder.
*
@@ -148,7 +173,8 @@ public final class ExpressionParser {
* @since 2019-03-17
*/
public ExpressionParser build() {
- return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, this.binaryOperators);
+ return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, this.binaryOperators,
+ this.spaceFunction);
}
}
@@ -330,34 +356,6 @@ public final class ExpressionParser {
throw new IllegalArgumentException("No matching bracket found.");
}
- public static void main(final String[] args) {
- final ExpressionParser numberParser = new ExpressionParser.Builder<>(Integer::parseInt)
- .addBinaryOperator("+", (o1, o2) -> o1 + o2, 0).addBinaryOperator("*", (o1, o2) -> o1 * o2, 1)
- .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 2).build();
- System.out.println(numberParser.convertExpressionToReversePolish("(1 + 2) ^ 5 * 3"));
- System.out.println(numberParser.parseExpression("(1 + 2) ^ 5 * 3")); // 729
- }
-
- /**
- * Swaps two elements in a list. Modifies the list passed in instead of returning a modified list.
- *
- * @param list
- * list to swap elements
- * @param firstIndex
- * index of first element to swap
- * @param otherIndex
- * index of other element to swap
- * @throws NullPointerException
- * if list is null
- * @since 2019-03-20
- */
- private static void swap(final List list, final int firstIndex, final int otherIndex) {
- Objects.requireNonNull(list, "list must not be null.");
- final E temp = list.get(firstIndex);
- list.set(firstIndex, list.get(otherIndex));
- list.set(otherIndex, temp);
- }
-
/**
* A function that obtains a parseable object from a string. For example, an integer {@code ExpressionParser} would
* use {@code Integer::parseInt}.
@@ -380,21 +378,33 @@ public final class ExpressionParser {
*/
private final Map> binaryOperators;
+ /**
+ * The operator for space, or null if spaces have no function.
+ *
+ * @since 2019-03-22
+ */
+ private final String spaceOperator;
+
/**
* Creates the {@code ExpressionParser}.
*
* @param objectObtainer
* function to get objects from strings
* @param unaryOperators
- * operators available to the parser
+ * unary operators available to the parser
+ * @param binaryOperators
+ * binary operators available to the parser
+ * @param spaceOperator
+ * operator used by spaces
* @since 2019-03-14
*/
private ExpressionParser(final Function objectObtainer,
final Map> unaryOperators,
- final Map> binaryOperators) {
+ final Map> binaryOperators, final String spaceOperator) {
this.objectObtainer = objectObtainer;
this.unaryOperators = unaryOperators;
this.binaryOperators = binaryOperators;
+ this.spaceOperator = spaceOperator;
}
/**
@@ -422,10 +432,26 @@ public final class ExpressionParser {
while (partialExpression.indexOf(OPENING_BRACKET) != -1) {
final int openingBracketPosition = partialExpression.indexOf(OPENING_BRACKET);
final int closingBracketPosition = findBracketPair(partialExpression, openingBracketPosition);
- components.addAll(Arrays.asList(partialExpression.substring(0, openingBracketPosition).split(" ")));
- components.add(this.convertExpressionToReversePolish(
- partialExpression.substring(openingBracketPosition + 1, closingBracketPosition)));
- partialExpression = partialExpression.substring(closingBracketPosition + 1);
+
+ // check for function
+ if (openingBracketPosition > 0 && partialExpression.charAt(openingBracketPosition - 1) != ' ') {
+ // function like sin(2) or tempF(32)
+ // find the position of the last space
+ int spacePosition = openingBracketPosition;
+ while (spacePosition >= 0 && partialExpression.charAt(spacePosition) != ' ') {
+ spacePosition--;
+ }
+ // then split the function into pre-function and function, using the space position
+ components.addAll(Arrays.asList(partialExpression.substring(0, spacePosition + 1).split(" ")));
+ components.add(partialExpression.substring(spacePosition + 1, closingBracketPosition + 1));
+ partialExpression = partialExpression.substring(closingBracketPosition + 1);
+ } else {
+ // normal brackets like (1 + 2) * (3 / 5)
+ components.addAll(Arrays.asList(partialExpression.substring(0, openingBracketPosition).split(" ")));
+ components.add(this.convertExpressionToReversePolish(
+ partialExpression.substring(openingBracketPosition + 1, closingBracketPosition)));
+ partialExpression = partialExpression.substring(closingBracketPosition + 1);
+ }
}
// add everything else
@@ -436,6 +462,16 @@ public final class ExpressionParser {
components.remove("");
}
+ // deal with space multiplication (x y)
+ if (this.spaceOperator != null) {
+ for (int i = 0; i < components.size() - 1; i++) {
+ if (this.getTokenType(components.get(i)) == TokenType.OBJECT
+ && this.getTokenType(components.get(i + 1)) == TokenType.OBJECT) {
+ components.add(++i, this.spaceOperator);
+ }
+ }
+ }
+
// turn the expression into reverse Polish
while (true) {
final int highestPriorityOperatorPosition = this.findHighestPriorityOperatorPosition(components);
@@ -472,7 +508,7 @@ public final class ExpressionParser {
}
return expressionRPN;
- // TODO method stub org.unitConverter.expressionParser.ExpressionParser.convertExpressionToPolish(expression)
+ // TODO document org.unitConverter.expressionParser.ExpressionParser.convertExpressionToPolish(expression)
}
/**
diff --git a/unitsfile.txt b/unitsfile.txt
index 78e51f7..2455c8a 100755
--- a/unitsfile.txt
+++ b/unitsfile.txt
@@ -136,7 +136,7 @@ steradian m^2 / m^2
sr steradian
degree 360 / tau * radian
deg degree
-° degree
+° degree
# Nonlinear units, which are not supported by the file reader and must be defined manually
# Use tempC(100) for 100 degrees Celsius
--
cgit v1.2.3
From 91ee53876aeeb52e980dd1fa976fae06d890ba19 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 10 Apr 2019 19:31:59 -0400
Subject: Removed AbstractUnit's unit counting functionnality.
The startup unit count is now performed by the UnitDatabase.
---
src/org/unitConverter/UnitsDatabase.java | 15 ++++--
.../converterGUI/UnitConverterGUI.java | 18 ++++++--
src/org/unitConverter/math/ExpressionParser.java | 3 ++
src/org/unitConverter/unit/AbstractUnit.java | 54 ----------------------
src/org/unitConverter/unit/BaseUnit.java | 8 ++++
5 files changed, 35 insertions(+), 63 deletions(-)
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 481ce93..290a425 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -32,7 +33,6 @@ import java.util.Set;
import org.unitConverter.dimension.UnitDimension;
import org.unitConverter.math.DecimalComparison;
import org.unitConverter.math.ExpressionParser;
-import org.unitConverter.unit.AbstractUnit;
import org.unitConverter.unit.BaseUnit;
import org.unitConverter.unit.DefaultUnitPrefix;
import org.unitConverter.unit.LinearUnit;
@@ -186,10 +186,7 @@ public final class UnitsDatabase {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
- AbstractUnit.incrementUnitCounter();
- if (unit instanceof BaseUnit) {
- AbstractUnit.incrementBaseUnitCounter();
- }
+
this.addUnit(name, unit);
}
}
@@ -563,6 +560,14 @@ public final class UnitsDatabase {
return Collections.unmodifiableSet(this.units.keySet());
}
+ /**
+ * @return an immutable set of all of the units in this database, ignoring prefixes.
+ * @since 2019-04-10
+ */
+ public Set prefixlessUnitSet() {
+ return Collections.unmodifiableSet(new HashSet<>(this.units.values()));
+ }
+
/**
* @return an immutable set of all of the prefix names in this database
* @since 2019-01-14
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 867211c..fd40ff4 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -47,7 +47,7 @@ import javax.swing.ListSelectionModel;
import org.unitConverter.UnitsDatabase;
import org.unitConverter.dimension.StandardDimensions;
import org.unitConverter.dimension.UnitDimension;
-import org.unitConverter.unit.AbstractUnit;
+import org.unitConverter.unit.BaseUnit;
import org.unitConverter.unit.NonlinearUnits;
import org.unitConverter.unit.SI;
import org.unitConverter.unit.Unit;
@@ -143,8 +143,13 @@ final class UnitConverterGUI {
this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet()));
this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator
- System.out.printf("Successfully loaded %d units (%d base units)", AbstractUnit.getUnitCount(),
- AbstractUnit.getBaseUnitCount());
+ // a Predicate that returns true iff the argument is a full base unit
+ final Predicate isFullBase = unit -> unit instanceof BaseUnit && ((BaseUnit) unit).isFullBase();
+
+ // print out unit counts
+ System.out.printf("Successfully loaded %d units with %d unit names (%d base units).%n",
+ this.units.prefixlessUnitSet().size(), this.units.prefixlessUnitNameSet().size(),
+ this.units.prefixlessUnitSet().stream().filter(isFullBase).count());
}
/**
@@ -162,6 +167,11 @@ final class UnitConverterGUI {
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.");
+ return;
+ }
+
// try to parse from
final Unit from;
try {
@@ -175,8 +185,8 @@ final class UnitConverterGUI {
// try to parse to
final Unit to;
try {
- // if it's a unit, convert to that
if (this.units.containsUnitName(toUnitString)) {
+ // if it's a unit, convert to that
to = this.units.getUnit(toUnitString);
} else {
to = this.units.getUnitFromExpression(toUnitString);
diff --git a/src/org/unitConverter/math/ExpressionParser.java b/src/org/unitConverter/math/ExpressionParser.java
index f34a0c2..b56fa71 100644
--- a/src/org/unitConverter/math/ExpressionParser.java
+++ b/src/org/unitConverter/math/ExpressionParser.java
@@ -479,6 +479,9 @@ public final class ExpressionParser {
break;
}
+ // swap components based on what kind of operator there is
+ // 1 + 2 becomes 2 1 +
+ // - 1 becomes 1 -
switch (this.getTokenType(components.get(highestPriorityOperatorPosition))) {
case UNARY_OPERATOR:
final String unaryOperator = components.remove(highestPriorityOperatorPosition);
diff --git a/src/org/unitConverter/unit/AbstractUnit.java b/src/org/unitConverter/unit/AbstractUnit.java
index 6088960..a0d6f7e 100644
--- a/src/org/unitConverter/unit/AbstractUnit.java
+++ b/src/org/unitConverter/unit/AbstractUnit.java
@@ -28,60 +28,6 @@ import org.unitConverter.dimension.UnitDimension;
* @since v0.1.0
*/
public abstract class AbstractUnit implements Unit {
- /**
- * The number of units created, including base units.
- *
- * @since 2019-01-02
- * @since v0.1.0
- */
- private static long unitCount = 0;
-
- /**
- * The number of base units created.
- *
- * @since 2019-01-02
- * @since v0.1.0
- */
- private static long baseUnitCount = 0;
-
- /**
- * @return number of base units created
- * @since 2019-01-02
- * @since v0.1.0
- */
- public static final long getBaseUnitCount() {
- return baseUnitCount;
- }
-
- /**
- * @return number of units created
- * @since 2019-01-02
- * @since v0.1.0
- */
- public static final long getUnitCount() {
- return unitCount;
- }
-
- /**
- * Increments the number of base units.
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public static final void incrementBaseUnitCounter() {
- baseUnitCount++;
- }
-
- /**
- * Increments the number of units.
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public static final void incrementUnitCounter() {
- unitCount++;
- }
-
/**
* The dimension, or what the unit measures.
*
diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
index 2def48e..643272f 100755
--- a/src/org/unitConverter/unit/BaseUnit.java
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -84,6 +84,14 @@ public final class BaseUnit extends LinearUnit {
return new BaseUnit(this.getDimension().dividedBy(divisor.getDimension()), this.getSystem());
}
+ /**
+ * @return true if the unit is a "full base" unit like the metre or second.
+ * @since 2019-04-10
+ */
+ public final boolean isFullBase() {
+ return this.isFullBase;
+ }
+
/**
* Returns the product of this unit and another.
*
--
cgit v1.2.3
From ec036fdad931fbbd7dec28b864150f8668e91b41 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Wed, 10 Apr 2019 21:00:49 -0400
Subject: Edited dimension database code and improved comments.
getDimension() now works with exponents,
Added a dimension parser,
comments can now be in the middle of lines
---
CHANGELOG.org | 1 +
src/org/unitConverter/UnitsDatabase.java | 181 +++++++++++++++++++------------
2 files changed, 115 insertions(+), 67 deletions(-)
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 95dc57a..8a79c46 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -6,6 +6,7 @@ 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
*** Added
- GUI for a selection-based unit converter
- The UnitDatabase now stores dimensions.
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 290a425..69b25d8 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -48,6 +48,32 @@ import org.unitConverter.unit.UnitPrefix;
* @since v0.1.0
*/
public final class UnitsDatabase {
+ /**
+ * The exponent operator
+ *
+ * @param base
+ * base of exponentiation
+ * @param exponentUnit
+ * exponent
+ * @return result
+ * @since 2019-04-10
+ */
+ private static final LinearUnit exponent(final LinearUnit base, final LinearUnit exponentUnit) {
+ // exponent function - first check if o2 is a number,
+ if (exponentUnit.getBase().equals(SI.SI.getBaseUnit(UnitDimension.EMPTY))) {
+ // then check if it is an integer,
+ final double exponent = exponentUnit.getConversionFactor();
+ if (DecimalComparison.equals(exponent % 1, 0))
+ // then exponentiate
+ return base.toExponent((int) (exponent % 1 + 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.
*
@@ -80,22 +106,12 @@ public final class UnitsDatabase {
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.dividedBy(o2), 1).addBinaryOperator("^", (o1, o2) -> {
- // exponent function - first check if o2 is a number,
- if (o2.getBase().equals(SI.SI.getBaseUnit(UnitDimension.EMPTY))) {
- // then check if it is an integer,
- final double exponent = o2.getConversionFactor();
- if (DecimalComparison.equals(exponent % 1, 0))
- // then exponentiate
- return o1.toExponent((int) (exponent % 1 + 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.");
- }, 2).build();
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
+ .addBinaryOperator("^", UnitsDatabase::exponent, 2).build();
+
+ private final ExpressionParser unitDimensionParser = new ExpressionParser.Builder<>(
+ this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0).addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build();
/**
* Creates the {@code UnitsDatabase}.
@@ -118,7 +134,7 @@ public final class UnitsDatabase {
*
* Allowed exceptions:
*
- *
Any line that begins with the '#' character is considered a comment and ignored.
+ *
Anything after a '#' character is considered a comment and ignored.
*
Blank lines are also ignored
*
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
@@ -140,56 +156,7 @@ public final class UnitsDatabase {
// while the reader has lines to read, read a line, then parse it, then add it
long lineCounter = 0;
while (reader.ready()) {
- final String line = reader.readLine();
- lineCounter++;
-
- // ignore lines that start with a # sign - they're comments
- if (line.startsWith("#") || line.isEmpty()) {
- continue;
- }
-
- // 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);
- }
- }
+ this.addFromLine(reader.readLine(), ++lineCounter);
}
} catch (final FileNotFoundException e) {
throw new IllegalArgumentException("Could not find file " + file, e);
@@ -214,6 +181,67 @@ public final class UnitsDatabase {
Objects.requireNonNull(dimension, "dimension 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
+ * @since 2019-04-10
+ */
+ private void addFromLine(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);
+ 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);
+ }
+ }
+ }
+
/**
* Adds a unit prefix to the database.
*
@@ -316,12 +344,31 @@ public final class UnitsDatabase {
/**
* Gets a unit dimension from the database using its name.
*
+ *
+ * This method accepts exponents, like "L^3"
+ *
+ *
* @param name
* dimension's name
* @return dimension
* @since 2019-03-14
*/
public UnitDimension getDimension(final String name) {
+ Objects.requireNonNull(name, "name must not be null.");
+ if (name.contains("^")) {
+ final String[] baseAndExponent = name.split("\\^");
+
+ final UnitDimension base = this.getDimension(baseAndExponent[0]);
+
+ final int exponent;
+ try {
+ 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);
}
--
cgit v1.2.3
From 8e613844ae19a4dea2089ac34c1f0ae650eaeae7 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 13 Apr 2019 10:05:08 -0400
Subject: 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.
---
CHANGELOG.org | 4 +-
dimensionfile.txt | 13 +
src/org/unitConverter/UnitsDatabase.java | 265 +++++++++++++++------
.../converterGUI/UnitConverterGUI.java | 30 ++-
src/org/unitConverter/unit/BaseUnit.java | 24 ++
src/org/unitConverter/unit/LinearUnit.java | 10 +
6 files changed, 275 insertions(+), 71 deletions(-)
create mode 100644 dimensionfile.txt
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
@@ -125,46 +125,6 @@ public final class UnitsDatabase {
this.dimensions = new HashMap<>();
}
- /**
- * Adds all units from a file, using data from the database to parse them.
- *
- * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
- * any number of tab characters.
- *
- *
- * Allowed exceptions:
- *
- *
Anything after a '#' character is considered a comment and ignored.
- *
Blank lines are also ignored
- *
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.
- *
- *
- * @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.
*
@@ -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);
}
}
@@ -276,6 +226,67 @@ public final class UnitsDatabase {
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
+ * @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.
*
@@ -372,6 +383,44 @@ public final class UnitsDatabase {
return this.dimensions.get(name);
}
+ /**
+ * Uses the database's data to parse an expression into a unit dimension
+ *
+ * The expression is a series of any of the following:
+ *
+ *
The name of a unit dimension, which multiplies or divides the result based on preceding operators
+ *
The operators '*' and '/', which multiply and divide (note that just putting two unit dimensions next to each
+ * other is equivalent to multiplication)
+ *
The operator '^' which exponentiates. Exponents must be integers.
+ *
+ *
+ * @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}.
@@ -598,6 +647,86 @@ public final class UnitsDatabase {
return this.unitExpressionParser.parseExpression(modifiedExpression);
}
+ /**
+ * Adds all dimensions from a file, using data from the database to parse them.
+ *
+ * Each line in the file should consist of a name and an expression (parsed by getDimensionFromExpression) separated
+ * by any number of tab characters.
+ *
+ *
+ * Allowed exceptions:
+ *
+ *
Anything after a '#' character is considered a comment and ignored.
+ *
Blank lines are also ignored
+ *
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.
+ *
+ *
+ * @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.
+ *
+ * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
+ * any number of tab characters.
+ *
+ *
+ * Allowed exceptions:
+ *
+ *
Anything after a '#' character is considered a comment and ignored.
+ *
Blank lines are also ignored
+ *
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.
+ *
+ *
+ * @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
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 prefixNamesFiltered;
+ /** The names of all of the dimensions */
+ private final List dimensionNames;
+
private final Comparator 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 isFullBase = unit -> unit instanceof BaseUnit && ((BaseUnit) unit).isFullBase();
@@ -222,6 +238,14 @@ final class UnitConverterGUI {
this.view.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString));
}
+ /**
+ * @return a list of all of the unit dimensions
+ * @since 2019-04-13
+ */
+ public final List dimensionNameList() {
+ return this.dimensionNames;
+ }
+
/**
* Filters the filtered model for units
*
@@ -531,8 +555,10 @@ final class UnitConverterGUI {
inBetweenPanel.setLayout(new BorderLayout());
{ // dimension selector
+ final List dimensionNameList = this.presenter.dimensionNameList();
+ dimensionNameList.add(0, "Select a dimension...");
final JComboBox 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.
+ *
+ * {@code BaseUnit} does not have any public constructors or static factories. There are two ways to obtain
+ * {@code BaseUnit} instances.
+ *
+ *
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:
+ *
+ *
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:
+ *
+ *
+ *
*
* @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.
+ *
+ * {@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:
+ *
+ *
+ * LinearUnit foot = METRE.times(0.3048);
+ *
+ *
+ * (where {@code METRE} is a {@code BaseUnit} instance)
+ *
*
* @author Adrien Hopkins
* @since 2018-12-22
--
cgit v1.2.3
From e0c5021a9ba85debf0c0722d78f75a0dbcc8376b Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 13 Apr 2019 12:17:14 -0400
Subject: Implemented the dimension-based converter.
Also added a search box list, and fixed a bug with dimension exponentiation.
---
dimensionfile.txt | 10 +-
src/org/unitConverter/UnitsDatabase.java | 2 +-
.../converterGUI/DelegateListModel.java | 10 +
.../converterGUI/MutablePredicate.java | 60 ++++++
.../unitConverter/converterGUI/SearchBoxList.java | 205 +++++++++++++++++++++
.../converterGUI/UnitConverterGUI.java | 180 ++++++++++++++----
unitsfile.txt | 20 +-
7 files changed, 444 insertions(+), 43 deletions(-)
create mode 100644 src/org/unitConverter/converterGUI/MutablePredicate.java
create mode 100644 src/org/unitConverter/converterGUI/SearchBoxList.java
diff --git a/dimensionfile.txt b/dimensionfile.txt
index d3c068c..7a1da10 100644
--- a/dimensionfile.txt
+++ b/dimensionfile.txt
@@ -4,10 +4,14 @@
# ! 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.
+# I have excluded electric current and quantity since their units are exclusively SI.
+
LENGTH !
MASS !
TIME !
-ELECTRIC_CURRENT !
TEMPERATURE !
-QUANTITY !
-LUMINOUS_INTENSITY !
\ No newline at end of file
+LUMINOUS_INTENSITY !
+
+# Derived Dimensions
+VELOCITY LENGTH / TIME
+ENERGY MASS * VELOCITY^2
\ No newline at end of file
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 626f145..c3d3131 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -65,7 +65,7 @@ public final class UnitsDatabase {
final double exponent = exponentUnit.getConversionFactor();
if (DecimalComparison.equals(exponent % 1, 0))
// then exponentiate
- return base.toExponent((int) (exponent % 1 + 0.5));
+ return base.toExponent((int) (exponent + 0.5));
else
// not an integer
throw new UnsupportedOperationException("Decimal exponents are currently not supported.");
diff --git a/src/org/unitConverter/converterGUI/DelegateListModel.java b/src/org/unitConverter/converterGUI/DelegateListModel.java
index e375126..b80f63d 100755
--- a/src/org/unitConverter/converterGUI/DelegateListModel.java
+++ b/src/org/unitConverter/converterGUI/DelegateListModel.java
@@ -16,6 +16,7 @@
*/
package org.unitConverter.converterGUI;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -49,6 +50,15 @@ final class DelegateListModel extends AbstractListModel implements List
*/
private final List delegate;
+ /**
+ * Creates an empty {@code DelegateListModel}.
+ *
+ * @since 2019-04-13
+ */
+ public DelegateListModel() {
+ this(new ArrayList<>());
+ }
+
/**
* Creates the {@code DelegateListModel}.
*
diff --git a/src/org/unitConverter/converterGUI/MutablePredicate.java b/src/org/unitConverter/converterGUI/MutablePredicate.java
new file mode 100644
index 0000000..157903c
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/MutablePredicate.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.converterGUI;
+
+import java.util.function.Predicate;
+
+/**
+ * A container for a predicate, which can be changed later.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ */
+final class MutablePredicate implements Predicate {
+ private Predicate predicate;
+
+ /**
+ * Creates the {@code MutablePredicate}.
+ *
+ * @since 2019-04-13
+ */
+ public MutablePredicate(final Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ /**
+ * @return predicate
+ * @since 2019-04-13
+ */
+ public final Predicate getPredicate() {
+ return this.predicate;
+ }
+
+ /**
+ * @param predicate
+ * new value of predicate
+ * @since 2019-04-13
+ */
+ public final void setPredicate(final Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public boolean test(final T t) {
+ return this.predicate.test(t);
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/SearchBoxList.java b/src/org/unitConverter/converterGUI/SearchBoxList.java
new file mode 100644
index 0000000..7d3b748
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/SearchBoxList.java
@@ -0,0 +1,205 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.unitConverter.converterGUI;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.Predicate;
+
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ */
+final class SearchBoxList extends JPanel {
+
+ /**
+ * @since 2019-04-13
+ */
+ private static final long serialVersionUID = 6226930279415983433L;
+
+ /**
+ * The text to place in an empty search box.
+ */
+ private static final String EMPTY_TEXT = "Search...";
+ /**
+ * The color to use for an empty foreground.
+ */
+ private static final Color EMPTY_FOREGROUND = new Color(192, 192, 192);
+
+ // the components
+ private final Collection itemsToFilter;
+ private final DelegateListModel listModel;
+ private final JTextField searchBox;
+ private final JList searchItems;
+
+ private boolean searchBoxEmpty = true;
+
+ // I need to do this because, for some reason, Swing is auto-focusing my search box without triggering a focus
+ // event.
+ private boolean searchBoxFocused = false;
+
+ private Predicate searchFilter = o -> true;
+
+ /**
+ * Creates the {@code SearchBoxList}.
+ *
+ * @since 2019-04-13
+ */
+ public SearchBoxList(final Collection itemsToFilter) {
+ super(new BorderLayout(), true);
+ this.itemsToFilter = itemsToFilter;
+
+ // create the components
+ this.listModel = new DelegateListModel<>(new ArrayList<>(itemsToFilter));
+ this.searchItems = new JList<>(this.listModel);
+
+ this.searchBox = new JTextField(EMPTY_TEXT);
+ this.searchBox.setForeground(EMPTY_FOREGROUND);
+
+ // add them to the panel
+ this.add(this.searchBox, BorderLayout.PAGE_START);
+ this.add(new JScrollPane(this.searchItems), BorderLayout.CENTER);
+
+ // set up the search box
+ this.searchBox.addFocusListener(new FocusListener() {
+ @Override
+ public void focusGained(final FocusEvent e) {
+ SearchBoxList.this.searchBoxFocusGained(e);
+ }
+
+ @Override
+ public void focusLost(final FocusEvent e) {
+ SearchBoxList.this.searchBoxFocusLost(e);
+ }
+ });
+
+ this.searchBox.addCaretListener(e -> this.searchBoxTextChanged());
+ this.searchBoxEmpty = true;
+ }
+
+ /**
+ * Adds an additional filter for searching.
+ *
+ * @param filter
+ * filter to add.
+ * @since 2019-04-13
+ */
+ public void addSearchFilter(final Predicate filter) {
+ this.searchFilter = this.searchFilter.and(filter);
+ }
+
+ /**
+ * Resets the search filter.
+ *
+ * @since 2019-04-13
+ */
+ public void clearSearchFilters() {
+ this.searchFilter = o -> true;
+ }
+
+ /**
+ * @return value selected in item list
+ * @since 2019-04-13
+ */
+ public String getSelectedValue() {
+ return this.searchItems.getSelectedValue();
+ }
+
+ /**
+ * Re-applies the filters.
+ *
+ * @since 2019-04-13
+ */
+ public void reapplyFilter() {
+ final String searchText = this.searchBoxEmpty ? "" : this.searchBox.getText();
+ final FilterComparator comparator = new FilterComparator(searchText);
+
+ this.listModel.clear();
+ this.itemsToFilter.forEach(string -> {
+ if (string.toLowerCase().contains(searchText.toLowerCase())) {
+ this.listModel.add(string);
+ }
+ });
+
+ // applies the custom filters
+ this.listModel.removeIf(this.searchFilter.negate());
+
+ // sorts the remaining items
+ this.listModel.sort(comparator);
+ }
+
+ /**
+ * Runs whenever the search box gains focus.
+ *
+ * @param e
+ * focus event
+ * @since 2019-04-13
+ */
+ private void searchBoxFocusGained(final FocusEvent e) {
+ this.searchBoxFocused = true;
+ if (this.searchBoxEmpty) {
+ this.searchBox.setText("");
+ this.searchBox.setForeground(Color.BLACK);
+ }
+ }
+
+ /**
+ * Runs whenever the search box loses focus.
+ *
+ * @param e
+ * focus event
+ * @since 2019-04-13
+ */
+ private void searchBoxFocusLost(final FocusEvent e) {
+ this.searchBoxFocused = false;
+ if (this.searchBoxEmpty) {
+ this.searchBox.setText(EMPTY_TEXT);
+ this.searchBox.setForeground(EMPTY_FOREGROUND);
+ }
+ }
+
+ private void searchBoxTextChanged() {
+ if (this.searchBoxFocused) {
+ this.searchBoxEmpty = this.searchBox.getText().equals("");
+ }
+ final String searchText = this.searchBoxEmpty ? "" : this.searchBox.getText();
+ final FilterComparator comparator = new FilterComparator(searchText);
+
+ // initialize list with items that match the filter then sort
+ this.listModel.clear();
+ this.itemsToFilter.forEach(string -> {
+ if (string.toLowerCase().contains(searchText.toLowerCase())) {
+ this.listModel.add(string);
+ }
+ });
+
+ // applies the custom filters
+ this.listModel.removeIf(this.searchFilter.negate());
+
+ // sorts the remaining items
+ this.listModel.sort(comparator);
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 4f5ebeb..9314510 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -25,6 +25,7 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.Set;
import java.util.function.Predicate;
import javax.swing.BorderFactory;
@@ -116,9 +117,7 @@ final class UnitConverterGUI {
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"));
@@ -238,6 +237,52 @@ final class UnitConverterGUI {
this.view.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString));
}
+ /**
+ * Converts in the dimension-based converter
+ *
+ * @since 2019-04-13
+ */
+ public final void convertDimensionBased() {
+ final String fromSelection = this.view.getFromSelection();
+ if (fromSelection == null) {
+ this.view.showErrorDialog("Error", "No unit selected in From field");
+ return;
+ }
+ final String toSelection = this.view.getToSelection();
+ if (toSelection == null) {
+ this.view.showErrorDialog("Error", "No unit selected in To field");
+ return;
+ }
+
+ final Unit from = this.units.getUnit(fromSelection);
+ final Unit to = this.units.getUnit(toSelection);
+
+ final String input = this.view.getDimensionBasedInput();
+ if (input.equals("")) {
+ this.view.showErrorDialog("Error", "No value to convert entered.");
+ return;
+ }
+ final double beforeValue = Double.parseDouble(input);
+ final double value = to.convertFromBase(from.convertToBase(beforeValue));
+
+ // round value
+ final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures));
+ String output = bigValue.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);
+ }
+ }
+
+ this.view.setDimensionBasedOutputText(
+ String.format("%s %s = %s %s", input, fromSelection, output, toSelection));
+ }
+
/**
* @return a list of all of the unit dimensions
* @since 2019-04-13
@@ -365,6 +410,23 @@ final class UnitConverterGUI {
this.unitNamesFiltered.sort(new FilterComparator(filter));
}
+ /**
+ * Returns true if and only if the unit represented by {@code unitName} has the dimension represented by
+ * {@code dimensionName}.
+ *
+ * @param unitName
+ * name of unit to test
+ * @param dimensionName
+ * name of dimension to test
+ * @return whether unit has dimenision
+ * @since 2019-04-13
+ */
+ public boolean unitMatchesDimension(final String unitName, final String dimensionName) {
+ final Unit unit = this.units.getUnit(unitName);
+ final UnitDimension dimension = this.units.getDimension(dimensionName);
+ return unit.getDimension().equals(dimension);
+ }
+
/**
* Runs whenever a unit is selected in the viewer.
*
@@ -385,6 +447,10 @@ final class UnitConverterGUI {
this.view.setUnitTextBoxText(unit.toString());
}
}
+
+ public final Set unitNameSet() {
+ return this.units.prefixlessUnitNameSet();
+ }
}
private static class View {
@@ -405,6 +471,14 @@ final class UnitConverterGUI {
private final JTextField prefixFilterEntry;
/** The text box for prefix data in the prefix viewer */
private final JTextArea prefixTextBox;
+ /** The panel for "From" in the dimension-based converter */
+ private final SearchBoxList fromSearch;
+ /** The panel for "To" in the dimension-based converter */
+ private final SearchBoxList toSearch;
+ /** The panel for inputting values in the dimension-based converter */
+ private final JTextField valueInput;
+ /** The output area in the dimension-based converter */
+ private final JTextArea dimensionBasedOutput;
/** The "From" entry in the conversion panel */
private final JTextField fromEntry;
/** The "To" entry in the conversion panel */
@@ -430,6 +504,10 @@ final class UnitConverterGUI {
this.unitTextBox = new JTextArea();
this.prefixFilterEntry = new JTextField();
this.prefixTextBox = new JTextArea();
+ this.fromSearch = new SearchBoxList(this.presenter.unitNameSet());
+ this.toSearch = new SearchBoxList(this.presenter.unitNameSet());
+ this.valueInput = new JFormattedTextField(new DecimalFormat("###############0.################"));
+ this.dimensionBasedOutput = new JTextArea(2, 32);
this.fromEntry = new JTextField();
this.toEntry = new JTextField();
this.output = new JTextArea(2, 32);
@@ -440,6 +518,22 @@ final class UnitConverterGUI {
this.frame.pack();
}
+ /**
+ * @return value in dimension-based converter
+ * @since 2019-04-13
+ */
+ public String getDimensionBasedInput() {
+ return this.valueInput.getText();
+ }
+
+ /**
+ * @return selection in "From" selector in dimension-based converter
+ * @since 2019-04-13
+ */
+ public String getFromSelection() {
+ return this.fromSearch.getSelectedValue();
+ }
+
/**
* @return text in "From" box in converter panel
* @since 2019-01-15
@@ -467,6 +561,14 @@ final class UnitConverterGUI {
return this.prefixNameList.getSelectedIndex();
}
+ /**
+ * @return selection in "To" selector in dimension-based converter
+ * @since 2019-04-13
+ */
+ public String getToSelection() {
+ return this.toSearch.getSelectedValue();
+ }
+
/**
* @return text in "To" box in converter panel
* @since 2019-01-26
@@ -531,22 +633,17 @@ final class UnitConverterGUI {
inputPanel.setLayout(new GridLayout(1, 3));
- { // panel for From things
- final JPanel fromPanel = new JPanel();
- inputPanel.add(fromPanel);
+ final JComboBox dimensionSelector = new JComboBox<>(
+ this.presenter.dimensionNameList().toArray(new String[0]));
+ dimensionSelector.setSelectedItem("LENGTH");
- fromPanel.setLayout(new BorderLayout());
+ // handle dimension filter
+ final MutablePredicate dimensionFilter = new MutablePredicate<>(s -> true);
- { // search box for from
- final JTextField fromSearch = new JTextField("Search...");
- fromPanel.add(fromSearch, BorderLayout.PAGE_START);
- }
+ // panel for From things
+ inputPanel.add(this.fromSearch);
- { // list for From units
- final JList fromList = new JList<>();
- fromPanel.add(fromList, BorderLayout.CENTER);
- }
- }
+ this.fromSearch.addSearchFilter(dimensionFilter);
{ // for dimension selector and arrow that represents conversion
final JPanel inBetweenPanel = new JPanel();
@@ -555,10 +652,6 @@ final class UnitConverterGUI {
inBetweenPanel.setLayout(new BorderLayout());
{ // dimension selector
- final List dimensionNameList = this.presenter.dimensionNameList();
- dimensionNameList.add(0, "Select a dimension...");
- final JComboBox dimensionSelector = new JComboBox<>(
- dimensionNameList.toArray(new String[0]));
inBetweenPanel.add(dimensionSelector, BorderLayout.PAGE_START);
}
@@ -568,23 +661,25 @@ final class UnitConverterGUI {
}
}
- { // panel for To things
- final JPanel toPanel = new JPanel();
- inputPanel.add(toPanel);
+ // panel for To things
- toPanel.setLayout(new BorderLayout());
+ inputPanel.add(this.toSearch);
- { // search box for to
- final JTextField toSearch = new JTextField("Search...");
- toPanel.add(toSearch, BorderLayout.PAGE_START);
- }
+ this.toSearch.addSearchFilter(dimensionFilter);
- { // list for To units
- final JList toList = new JList<>();
- toPanel.add(toList, BorderLayout.CENTER);
- }
- }
+ // code for dimension filter
+ dimensionSelector.addItemListener(e -> {
+ 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()));
+ this.fromSearch.reapplyFilter();
+ this.toSearch.reapplyFilter();
}
{ // panel for submit and output, and also value entry
@@ -605,20 +700,20 @@ final class UnitConverterGUI {
}
{ // value to convert
- final JTextField valueInput = new JFormattedTextField(
- new DecimalFormat("###############0.################"));
- valueInputPanel.add(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());
}
{ // output of conversion
- final JLabel outputLabel = new JLabel();
- outputPanel.add(outputLabel);
+ outputPanel.add(this.dimensionBasedOutput);
+ this.dimensionBasedOutput.setEditable(false);
}
}
}
@@ -762,6 +857,17 @@ final class UnitConverterGUI {
}
}
+ /**
+ * Sets the text in the output of the dimension-based converter.
+ *
+ * @param text
+ * text to set
+ * @since 2019-04-13
+ */
+ public void setDimensionBasedOutputText(final String text) {
+ this.dimensionBasedOutput.setText(text);
+ }
+
/**
* Sets the text in the output of the conversion panel.
*
diff --git a/unitsfile.txt b/unitsfile.txt
index 2455c8a..14fb6fb 100755
--- a/unitsfile.txt
+++ b/unitsfile.txt
@@ -171,7 +171,7 @@ arcsecond 1 / 60 arcminute
arcsec arcsecond
# constants
-waterdensity 1 kilogram / litre
+waterdensity kilogram / litre
# Imperial length units
foot 0.3048 m
@@ -183,6 +183,10 @@ yd yard
mile 1760 yard
mi mile
+# Compressed notation
+kph km / hour
+mph mile / hour
+
# Imperial weight units
pound 0.45359237 kg
lb pound
@@ -231,4 +235,16 @@ metricpint 2 metriccup
pint metricpint
metricquart 2 metricpint
quart metricquart
-metricgallon 4 metricquart
\ No newline at end of file
+metricgallon 4 metricquart
+
+# Energy units
+calorie 4.18 J
+cal calorie
+Calorie kilocalorie
+Cal Calorie
+
+# Extra units to only include in the dimension-based converter
+m/s m / s
+km/h km / h
+ft/s foot / s
+mi/h mile / hour
\ No newline at end of file
--
cgit v1.2.3
From f0f4898f796b9cc26294ba9feb22692143d00a9e Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 13 Apr 2019 15:55:49 -0400
Subject: Unit prefixes now have math methods, and use the expression parser.
---
CHANGELOG.org | 3 +-
src/org/unitConverter/UnitsDatabase.java | 119 ++++++++++++-----------------
src/org/unitConverter/unit/UnitPrefix.java | 36 +++++++++
3 files changed, 85 insertions(+), 73 deletions(-)
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 46197dc..e7748ba 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -9,11 +9,12 @@ All notable changes in this project will be shown in this file.
- 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
+ - A selection-based unit converter which allows you to select two units, input a value, and convert.
- 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
+ - The UnitPrefix interface now provides default times, dividedBy and toExponent methods.
** v0.1.0
NOTE: At this stage, the API is subject to significant change.
*** Added
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index c3d3131..a7e6047 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -33,7 +33,6 @@ import java.util.Set;
import org.unitConverter.dimension.UnitDimension;
import org.unitConverter.math.DecimalComparison;
import org.unitConverter.math.ExpressionParser;
-import org.unitConverter.unit.BaseUnit;
import org.unitConverter.unit.DefaultUnitPrefix;
import org.unitConverter.unit.LinearUnit;
import org.unitConverter.unit.SI;
@@ -109,6 +108,21 @@ public final class UnitsDatabase {
.addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
.addBinaryOperator("^", UnitsDatabase::exponent, 2).build();
+ /**
+ * A parser that can parse unit prefix expressions
+ *
+ * @since 2019-04-13
+ */
+ private final ExpressionParser 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.
+ *
+ * @since 2019-04-13
+ */
private final ExpressionParser unitDimensionParser = new ExpressionParser.Builder<>(
this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0).addSpaceFunction("*")
.addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build();
@@ -462,7 +476,11 @@ public final class UnitsDatabase {
* @since v0.1.0
*/
public UnitPrefix getPrefix(final String name) {
- return this.prefixes.get(name);
+ try {
+ return new DefaultUnitPrefix(Double.parseDouble(name));
+ } catch (final NumberFormatException e) {
+ return this.prefixes.get(name);
+ }
}
/**
@@ -485,33 +503,20 @@ public final class UnitsDatabase {
public UnitPrefix getPrefixFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
- try {
- return new DefaultUnitPrefix(Double.parseDouble(expression));
- } catch (final NumberFormatException e) {
- if (expression.contains("^")) {
- final String[] baseAndExponent = expression.split("\\^");
+ // attempt to get a unit as an alias first
+ if (this.containsUnitName(expression))
+ return this.getPrefix(expression);
- final double base;
- try {
- base = Double.parseDouble(baseAndExponent[0]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Base of exponientation must be a number.");
- }
+ // force operators to have spaces
+ String modifiedExpression = expression;
+ modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
+ modifiedExpression = modifiedExpression.replaceAll("/", " / ");
+ modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
- final int exponent;
- try {
- exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponent must be an integer.");
- }
+ // fix broken spaces
+ modifiedExpression = modifiedExpression.replaceAll(" +", " ");
- return new DefaultUnitPrefix(Math.pow(base, exponent));
- } else {
- if (!this.containsPrefixName(expression))
- throw new IllegalArgumentException("Unrecognized prefix name \"" + expression + "\".");
- return this.getPrefix(expression);
- }
- }
+ return this.prefixExpressionParser.parseExpression(modifiedExpression);
}
/**
@@ -541,57 +546,27 @@ public final class UnitsDatabase {
final double value = Double.parseDouble(name);
return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(value);
} catch (final NumberFormatException e) {
- if (name.contains("^")) {
- final String[] baseAndExponent = name.split("\\^");
-
- LinearUnit base;
- try {
- base = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(baseAndExponent[0]));
- } catch (final NumberFormatException e2) {
- final Unit unit = this.getUnit(baseAndExponent[0]);
- if (unit instanceof LinearUnit) {
- base = (LinearUnit) unit;
- } else
- throw new IllegalArgumentException("Base of exponientation must be a linear or base unit.");
- }
-
- final int exponent;
- try {
- exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
- } catch (final NumberFormatException e2) {
- throw new IllegalArgumentException("Exponent must be an integer.");
- }
-
- final LinearUnit exponentiated = base.toExponent(exponent);
- if (exponentiated.getConversionFactor() == 1)
- return exponentiated.getSystem().getBaseUnit(exponentiated.getDimension());
- else
- return exponentiated;
- } else {
- for (final String prefixName : this.prefixNameSet()) {
- // check for a prefix
- if (name.startsWith(prefixName)) {
- // prefix found! Make sure what comes after it is actually a unit!
- final String prefixless = name.substring(prefixName.length());
- if (this.containsUnitName(prefixless)) {
- // yep, it's a proper prefix! Get the unit!
- final Unit unit = this.getUnit(prefixless);
- final UnitPrefix prefix = this.getPrefix(prefixName);
-
- // Prefixes only work with linear and base units, so make sure it's one of those
- if (unit instanceof LinearUnit) {
- final LinearUnit linearUnit = (LinearUnit) unit;
- return linearUnit.times(prefix.getMultiplier());
- } else if (unit instanceof BaseUnit) {
- final BaseUnit baseUnit = (BaseUnit) unit;
- return baseUnit.times(prefix.getMultiplier());
- }
+ for (final String prefixName : this.prefixNameSet()) {
+ // check for a prefix
+ if (name.startsWith(prefixName)) {
+ // prefix found! Make sure what comes after it is actually a unit!
+ final String prefixless = name.substring(prefixName.length());
+ if (this.containsUnitName(prefixless)) {
+ // yep, it's a proper prefix! Get the unit!
+ final Unit unit = this.getUnit(prefixless);
+ final UnitPrefix prefix = this.getPrefix(prefixName);
+
+ // Prefixes only work with linear and base units, so make sure it's one of those
+ if (unit instanceof LinearUnit) {
+ final LinearUnit linearUnit = (LinearUnit) unit;
+ return linearUnit.withPrefix(prefix);
}
}
}
- return this.units.get(name);
}
+ return this.units.get(name);
}
+
}
/**
diff --git a/src/org/unitConverter/unit/UnitPrefix.java b/src/org/unitConverter/unit/UnitPrefix.java
index 289e60f..a1609c6 100755
--- a/src/org/unitConverter/unit/UnitPrefix.java
+++ b/src/org/unitConverter/unit/UnitPrefix.java
@@ -24,10 +24,46 @@ package org.unitConverter.unit;
* @since v0.1.0
*/
public interface UnitPrefix {
+ /**
+ * Divides this prefix by {@code other}.
+ *
+ * @param other
+ * prefix to divide by
+ * @return quotient of prefixes
+ * @since 2019-04-13
+ */
+ default UnitPrefix dividedBy(final UnitPrefix other) {
+ return new DefaultUnitPrefix(this.getMultiplier() / other.getMultiplier());
+ }
+
/**
* @return this prefix's multiplier
* @since 2019-01-14
* @since v0.1.0
*/
double getMultiplier();
+
+ /**
+ * Multiplies this prefix by {@code other}.
+ *
+ * @param other
+ * prefix to multiply by
+ * @return product of prefixes
+ * @since 2019-04-13
+ */
+ default UnitPrefix times(final UnitPrefix other) {
+ return new DefaultUnitPrefix(this.getMultiplier() * other.getMultiplier());
+ }
+
+ /**
+ * Raises this prefix to an exponent.
+ *
+ * @param exponent
+ * exponent to raise to
+ * @return result of exponentiation.
+ * @since 2019-04-13
+ */
+ default UnitPrefix toExponent(final double exponent) {
+ return new DefaultUnitPrefix(Math.pow(getMultiplier(), exponent));
+ }
}
--
cgit v1.2.3
From 63dd50e5d7a5daa0bcbdd00608543d4572c870ea Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 13 Apr 2019 19:48:25 -0400
Subject: Edited the UnitsDatabase API; it now favours prefixless units.
---
CHANGELOG.org | 2 +
src/org/unitConverter/UnitsDatabase.java | 753 ++++++++++++++++++---
.../converterGUI/UnitConverterGUI.java | 18 +-
src/org/unitConverter/math/ExpressionParser.java | 2 -
4 files changed, 689 insertions(+), 86 deletions(-)
diff --git a/CHANGELOG.org b/CHANGELOG.org
index e7748ba..db9766b 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -3,6 +3,8 @@ All notable changes in this project will be shown in this file.
** Unreleased
*** Changed
+ - When searching for units, units with no prefixes are searched for before prefixed units
+ - Smaller prefixes are searched for before larger prefixes
- Moved project to Maven
- Downgraded JUnit to 4.11
- BaseUnit is now a subclass of LinearUnit
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index a7e6047..9749e9c 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -21,14 +21,20 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
+import java.util.AbstractSet;
+import java.util.ArrayList;
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;
import java.util.Objects;
import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
import org.unitConverter.dimension.UnitDimension;
import org.unitConverter.math.DecimalComparison;
@@ -40,13 +46,652 @@ import org.unitConverter.unit.Unit;
import org.unitConverter.unit.UnitPrefix;
/**
- * A database of units and prefixes, and their names.
+ * A database of units, prefixes and dimensions, and their names.
*
* @author Adrien Hopkins
* @since 2019-01-07
* @since v0.1.0
*/
public final class UnitsDatabase {
+ /**
+ * A map for units that allows the use of prefixes.
+ *
+ * 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.
+ *
+ *
+ * The rules for applying prefixes onto units are the following:
+ *
+ *
Prefixes can only be applied to linear units.
+ *
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.
+ *
Shorter prefixes are preferred to longer prefixes. So, if you have units "BC" and "C", and prefixes "AB" and
+ * "A", inputting "ABC" will return the unit "BC" with the prefix "A", not "C" with the prefix "AB".
+ *
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ */
+ private static final class PrefixedUnitMap implements Map {
+ /**
+ * The class used for entry sets.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ */
+ private static final class PrefixedUnitEntrySet extends AbstractSet> {
+ // the map that created this set
+ private final PrefixedUnitMap map;
+
+ /**
+ * Creates the {@code PrefixedUnitNameSet}.
+ *
+ * @param map
+ * @since 2019-04-13
+ */
+ public PrefixedUnitEntrySet(final PrefixedUnitMap map) {
+ this.map = map;
+ }
+
+ @Override
+ public boolean add(final Map.Entry e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(final Collection extends Map.Entry> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ // get the entry
+ final Entry entry;
+
+ try {
+ // This is OK because I'm in a try-catch block.
+ @SuppressWarnings("unchecked")
+ final Entry tempEntry = (Entry) o;
+ entry = tempEntry;
+ } catch (final ClassCastException e) {
+ 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());
+ }
+
+ @Override
+ public boolean containsAll(final Collection> c) {
+ for (final Object o : c)
+ if (!this.contains(o))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.map.isEmpty();
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return new Iterator>() {
+ // position in the unit list
+ int unitNamePosition = -1;
+ // the indices of the prefixes attached to the current unit
+ List prefixCoordinates = new ArrayList<>();
+
+ List unitNames = new ArrayList<>(PrefixedUnitEntrySet.this.map.units.keySet());
+ List prefixNames = new ArrayList<>(PrefixedUnitEntrySet.this.map.prefixes.keySet());
+
+ @Override
+ public boolean hasNext() {
+ if (this.unitNames.isEmpty())
+ return false;
+ else {
+ if (this.prefixNames.isEmpty())
+ return this.unitNamePosition >= this.unitNames.size() - 1;
+ else
+ return true;
+ }
+ }
+
+ @Override
+ public Entry next() {
+ // increment unit name position
+ this.unitNamePosition++;
+
+ // if I have prefixes, ensure I'm not using a nonlinear unit
+ // since all of the unprefixed stuff is done, just remove nonlinear units
+ if (!this.prefixCoordinates.isEmpty()) {
+ while (!(PrefixedUnitEntrySet.this.map
+ .get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
+ this.unitNames.remove(this.unitNamePosition);
+ }
+ }
+
+ // carry over
+ if (!this.prefixNames.isEmpty() && this.unitNamePosition >= this.unitNames.size() - 1) {
+ // handle prefix position
+ this.unitNamePosition = 0;
+ int i = this.prefixCoordinates.size() - 1;
+ this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
+
+ while (this.prefixCoordinates.get(i) >= this.prefixNames.size() - 1) {
+ this.prefixCoordinates.set(i, 0);
+ i--;
+ if (i < 0) {
+ this.prefixCoordinates.add(0, 0);
+ }
+ }
+ }
+
+ final StringBuilder unitNameBuilder = new StringBuilder();
+ for (final int i : this.prefixCoordinates) {
+ unitNameBuilder.append(this.prefixNames.get(i));
+ }
+ unitNameBuilder.append(this.unitNames.get(this.unitNamePosition));
+
+ final String unitName = unitNameBuilder.toString();
+ return new Entry() {
+ @Override
+ public String getKey() {
+ return unitName;
+ }
+
+ @Override
+ public Unit getValue() {
+ return PrefixedUnitEntrySet.this.map.get(unitName);
+ }
+
+ @Override
+ public Unit setValue(final Unit value) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeIf(final Predicate super Entry> filter) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ if (this.map.units.isEmpty())
+ return 0;
+ else {
+ if (this.map.prefixes.isEmpty())
+ return this.map.units.size();
+ else
+ // infinite set
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ @Override
+ public Object[] toArray() {
+ if (this.map.units.isEmpty())
+ // finite, it will work
+ return super.toArray();
+ else {
+ if (this.map.prefixes.isEmpty())
+ // finite, it will work
+ return super.toArray();
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
+ }
+ }
+
+ @Override
+ public T[] toArray(final T[] a) {
+ if (this.map.units.isEmpty())
+ // finite, it will work
+ return super.toArray(a);
+ else {
+ if (this.map.prefixes.isEmpty())
+ // finite, it will work
+ return super.toArray(a);
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
+ }
+ }
+
+ }
+
+ /**
+ * The class used for unit name sets.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ */
+ private static final class PrefixedUnitNameSet extends AbstractSet {
+ // the map that created this set
+ private final PrefixedUnitMap map;
+
+ /**
+ * Creates the {@code PrefixedUnitNameSet}.
+ *
+ * @param map
+ * @since 2019-04-13
+ */
+ public PrefixedUnitNameSet(final PrefixedUnitMap map) {
+ this.map = map;
+ }
+
+ @Override
+ public boolean add(final String e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(final Collection extends String> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ return this.map.containsKey(o);
+ }
+
+ @Override
+ public boolean containsAll(final Collection> c) {
+ for (final Object o : c)
+ if (!this.contains(o))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.map.isEmpty();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ // position in the unit list
+ int unitNamePosition = -1;
+ // the indices of the prefixes attached to the current unit
+ List prefixCoordinates = new ArrayList<>();
+
+ List unitNames = new ArrayList<>(PrefixedUnitNameSet.this.map.units.keySet());
+ List prefixNames = new ArrayList<>(PrefixedUnitNameSet.this.map.prefixes.keySet());
+
+ @Override
+ public boolean hasNext() {
+ if (this.unitNames.isEmpty())
+ return false;
+ else {
+ if (this.prefixNames.isEmpty())
+ return this.unitNamePosition >= this.unitNames.size() - 1;
+ else
+ return true;
+ }
+ }
+
+ @Override
+ public String next() {
+ // increment unit name position
+ this.unitNamePosition++;
+
+ // if I have prefixes, ensure I'm not using a nonlinear unit
+ // since all of the unprefixed stuff is done, just remove nonlinear units
+ if (!this.prefixCoordinates.isEmpty()) {
+ while (!(PrefixedUnitNameSet.this.map
+ .get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
+ this.unitNames.remove(this.unitNamePosition);
+ }
+ }
+
+ // carry over
+ if (!this.prefixNames.isEmpty() && this.unitNamePosition >= this.unitNames.size() - 1) {
+ // handle prefix position
+ this.unitNamePosition = 0;
+ int i = this.prefixCoordinates.size() - 1;
+ this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
+
+ while (this.prefixCoordinates.get(i) >= this.prefixNames.size() - 1) {
+ this.prefixCoordinates.set(i, 0);
+ i--;
+ if (i < 0) {
+ this.prefixCoordinates.add(0, 0);
+ }
+ }
+ }
+
+ final StringBuilder unitName = new StringBuilder();
+ for (final int i : this.prefixCoordinates) {
+ unitName.append(this.prefixNames.get(i));
+ }
+ unitName.append(this.unitNames.get(this.unitNamePosition));
+ return unitName.toString();
+ }
+ };
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeIf(final Predicate super String> filter) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(final Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ if (this.map.units.isEmpty())
+ return 0;
+ else {
+ if (this.map.prefixes.isEmpty())
+ return this.map.units.size();
+ else
+ // infinite set
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ @Override
+ public Object[] toArray() {
+ if (this.map.units.isEmpty())
+ // finite, it will work
+ return super.toArray();
+ else {
+ if (this.map.prefixes.isEmpty())
+ // finite, it will work
+ return super.toArray();
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
+ }
+ }
+
+ @Override
+ public T[] toArray(final T[] a) {
+ if (this.map.units.isEmpty())
+ // finite, it will work
+ return super.toArray(a);
+ else {
+ if (this.map.prefixes.isEmpty())
+ // finite, it will work
+ return super.toArray(a);
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
+ }
+ }
+
+ }
+
+ /**
+ * The units stored in this collection, without prefixes.
+ *
+ * @since 2019-04-13
+ */
+ private final Map units;
+
+ /**
+ * The available prefixes for use.
+ *
+ * @since 2019-04-13
+ */
+ private final Map prefixes;
+
+ // caches
+ private Collection values = null;
+ private Set keySet = null;
+ private Set> entrySet = null;
+
+ /**
+ * Creates the {@code PrefixedUnitMap}.
+ *
+ * @param units
+ * @param prefixes
+ * @since 2019-04-13
+ */
+ public PrefixedUnitMap(final Map units, final Map 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();
+ }
+
+ @Override
+ public Unit compute(final String key,
+ final BiFunction super String, ? super Unit, ? extends Unit> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Unit computeIfAbsent(final String key, final Function super String, ? extends Unit> mappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Unit computeIfPresent(final String key,
+ final BiFunction super String, ? super Unit, ? extends Unit> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @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.");
+ final String unitName = (String) key;
+
+ // Then, look for the shortest prefix that is attached to a valid unit
+ String shortestPrefix = null;
+ int shortestLength = Integer.MAX_VALUE;
+
+ 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 shorter than the existing largest prefix (since I am looking for the smallest 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() < shortestLength) {
+ final String rest = unitName.substring(prefixName.length());
+ if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
+ shortestPrefix = prefixName;
+ shortestLength = prefixName.length();
+ }
+ }
+ }
+
+ return shortestPrefix != null;
+ }
+
+ @Override
+ public boolean containsValue(final Object value) {
+ return this.units.containsValue(value);
+ }
+
+ @Override
+ public Set> entrySet() {
+ if (this.entrySet == null) {
+ this.entrySet = new PrefixedUnitEntrySet(this);
+ }
+ 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.");
+ final String unitName = (String) key;
+
+ // Then, look for the shortest prefix that is attached to a valid unit
+ String shortestPrefix = null;
+ int shortestLength = Integer.MAX_VALUE;
+
+ 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 shorter than the existing largest prefix (since I am looking for the smallest 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() < shortestLength) {
+ final String rest = unitName.substring(prefixName.length());
+ if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
+ shortestPrefix = prefixName;
+ shortestLength = prefixName.length();
+ }
+ }
+ }
+
+ // if none found, returns null
+ if (shortestPrefix == null)
+ return null;
+ else {
+ // get necessary data
+ final String rest = unitName.substring(shortestLength);
+ // 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(shortestPrefix);
+
+ return unit.withPrefix(prefix);
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.units.isEmpty();
+ }
+
+ @Override
+ public Set keySet() {
+ if (this.keySet == null) {
+ this.keySet = new PrefixedUnitNameSet(this);
+ }
+ 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();
+ }
+
+ @Override
+ public Unit put(final String key, final Unit value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void putAll(final Map extends String, ? extends Unit> m) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Unit putIfAbsent(final String key, final Unit value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Unit remove(final Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(final Object key, final Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Unit replace(final String key, final Unit value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean replace(final String key, final Unit oldValue, final Unit newValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void replaceAll(final BiFunction super String, ? super Unit, ? extends Unit> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ if (this.units.isEmpty())
+ return 0;
+ else {
+ if (this.prefixes.isEmpty())
+ return this.units.size();
+ else
+ // infinite set
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ @Override
+ public Collection values() {
+ if (this.values == null) {
+ this.values = Collections.unmodifiableCollection(this.units.values());
+ }
+ return this.values;
+ }
+ }
+
/**
* The exponent operator
*
@@ -57,7 +702,7 @@ public final class UnitsDatabase {
* @return result
* @since 2019-04-10
*/
- private static final LinearUnit exponent(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.SI.getBaseUnit(UnitDimension.EMPTY))) {
// then check if it is an integer,
@@ -74,12 +719,12 @@ public final class UnitsDatabase {
}
/**
- * The units in this system.
+ * The units in this system, excluding prefixes.
*
* @since 2019-01-07
* @since v0.1.0
*/
- private final Map units;
+ private final Map prefixlessUnits;
/**
* The unit prefixes in this system.
@@ -96,6 +741,13 @@ public final class UnitsDatabase {
*/
private final Map dimensions;
+ /**
+ * A map mapping strings to units (including prefixes)
+ *
+ * @since 2019-04-13
+ */
+ private final Map units;
+
/**
* A parser that can parse unit expressions.
*
@@ -106,7 +758,7 @@ public final class UnitsDatabase {
.addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
.addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1).addSpaceFunction("*")
.addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
- .addBinaryOperator("^", UnitsDatabase::exponent, 2).build();
+ .addBinaryOperator("^", UnitsDatabase::exponentiateUnits, 2).build();
/**
* A parser that can parse unit prefix expressions
@@ -134,9 +786,10 @@ public final class UnitsDatabase {
* @since v0.1.0
*/
public UnitsDatabase() {
- this.units = new HashMap<>();
+ this.prefixlessUnits = new HashMap<>();
this.prefixes = new HashMap<>();
this.dimensions = new HashMap<>();
+ this.units = new PrefixedUnitMap(this.prefixlessUnits, this.prefixes);
}
/**
@@ -236,7 +889,7 @@ public final class UnitsDatabase {
* @since v0.1.0
*/
public void addUnit(final String name, final Unit unit) {
- this.units.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."));
}
@@ -313,19 +966,6 @@ public final class UnitsDatabase {
return this.dimensions.containsKey(name);
}
- /**
- * Tests if the database has a unit with this name, ignoring prefixes
- *
- * @param name
- * name to test
- * @return if database contains name
- * @since 2019-01-13
- * @since v0.1.0
- */
- public boolean containsPrefixlessUnitName(final String name) {
- return this.units.containsKey(name);
- }
-
/**
* Tests if the database has a unit prefix with this name.
*
@@ -349,21 +989,15 @@ public final class UnitsDatabase {
* @since v0.1.0
*/
public boolean containsUnitName(final String name) {
- // check for prefixes
- for (final String prefixName : this.prefixNameSet()) {
- if (name.startsWith(prefixName))
- if (this.containsUnitName(name.substring(prefixName.length())))
- return true;
- }
return this.units.containsKey(name);
}
/**
- * @return an immutable set of all of the dimension names in this database.
- * @since 2019-03-14
+ * @return a map mapping dimension names to dimensions
+ * @since 2019-04-13
*/
- public Set dimensionNameSet() {
- return Collections.unmodifiableSet(this.dimensions.keySet());
+ public Map dimensionMap() {
+ return Collections.unmodifiableMap(this.dimensions);
}
/**
@@ -519,19 +1153,6 @@ public final class UnitsDatabase {
return this.prefixExpressionParser.parseExpression(modifiedExpression);
}
- /**
- * Gets a unit from the database from its name, ignoring prefixes.
- *
- * @param name
- * unit's name
- * @return unit
- * @since 2019-01-10
- * @since v0.1.0
- */
- public Unit getPrefixlessUnit(final String name) {
- return this.units.get(name);
- }
-
/**
* Gets a unit from the database from its name, looking for prefixes.
*
@@ -546,24 +1167,6 @@ public final class UnitsDatabase {
final double value = Double.parseDouble(name);
return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(value);
} catch (final NumberFormatException e) {
- for (final String prefixName : this.prefixNameSet()) {
- // check for a prefix
- if (name.startsWith(prefixName)) {
- // prefix found! Make sure what comes after it is actually a unit!
- final String prefixless = name.substring(prefixName.length());
- if (this.containsUnitName(prefixless)) {
- // yep, it's a proper prefix! Get the unit!
- final Unit unit = this.getUnit(prefixless);
- final UnitPrefix prefix = this.getPrefix(prefixName);
-
- // Prefixes only work with linear and base units, so make sure it's one of those
- if (unit instanceof LinearUnit) {
- final LinearUnit linearUnit = (LinearUnit) unit;
- return linearUnit.withPrefix(prefix);
- }
- }
- }
- }
return this.units.get(name);
}
@@ -703,28 +1306,26 @@ public final class UnitsDatabase {
}
/**
- * @return an immutable set of all of the unit names in this database, ignoring prefixes
- * @since 2019-01-14
- * @since v0.1.0
+ * @return a map mapping prefix names to prefixes
+ * @since 2019-04-13
*/
- public Set prefixlessUnitNameSet() {
- return Collections.unmodifiableSet(this.units.keySet());
+ public Map prefixMap() {
+ return Collections.unmodifiableMap(this.prefixes);
}
/**
- * @return an immutable set of all of the units in this database, ignoring prefixes.
- * @since 2019-04-10
+ * @return a map mapping unit names to units, including prefixed names
+ * @since 2019-04-13
*/
- public Set prefixlessUnitSet() {
- return Collections.unmodifiableSet(new HashSet<>(this.units.values()));
+ public Map unitMap() {
+ return this.units; // PrefixedUnitMap is immutable so I don't need to make an unmodifiable map.
}
/**
- * @return an immutable set of all of the prefix names in this database
- * @since 2019-01-14
- * @since v0.1.0
+ * @return a map mapping unit names to units, ignoring prefixes
+ * @since 2019-04-13
*/
- public Set prefixNameSet() {
- return Collections.unmodifiableSet(this.prefixes.keySet());
+ public Map unitMapPrefixless() {
+ return Collections.unmodifiableMap(this.prefixlessUnits);
}
}
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 9314510..49a40d6 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -24,6 +24,7 @@ import java.math.MathContext;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
@@ -143,19 +144,19 @@ final class UnitConverterGUI {
return o1.compareTo(o2);
};
- this.unitNames = new ArrayList<>(this.units.prefixlessUnitNameSet());
+ this.unitNames = new ArrayList<>(this.units.unitMapPrefixless().keySet());
this.unitNames.sort(null); // sorts it using Comparable
- this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixlessUnitNameSet()));
+ this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.unitMapPrefixless().keySet()));
this.unitNamesFiltered.sort(null); // sorts it using Comparable
- this.prefixNames = new ArrayList<>(this.units.prefixNameSet());
+ this.prefixNames = new ArrayList<>(this.units.prefixMap().keySet());
this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator
- this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet()));
+ this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixMap().keySet()));
this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator
- this.dimensionNames = new DelegateListModel<>(new ArrayList<>(this.units.dimensionNameSet()));
+ this.dimensionNames = new DelegateListModel<>(new ArrayList<>(this.units.dimensionMap().keySet()));
this.dimensionNames.sort(null); // sorts it using Comparable
// a Predicate that returns true iff the argument is a full base unit
@@ -163,8 +164,9 @@ final class UnitConverterGUI {
// print out unit counts
System.out.printf("Successfully loaded %d units with %d unit names (%d base units).%n",
- this.units.prefixlessUnitSet().size(), this.units.prefixlessUnitNameSet().size(),
- this.units.prefixlessUnitSet().stream().filter(isFullBase).count());
+ new HashSet<>(this.units.unitMapPrefixless().values()).size(),
+ this.units.unitMapPrefixless().size(),
+ new HashSet<>(this.units.unitMapPrefixless().values()).stream().filter(isFullBase).count());
}
/**
@@ -449,7 +451,7 @@ final class UnitConverterGUI {
}
public final Set unitNameSet() {
- return this.units.prefixlessUnitNameSet();
+ return this.units.unitMapPrefixless().keySet();
}
}
diff --git a/src/org/unitConverter/math/ExpressionParser.java b/src/org/unitConverter/math/ExpressionParser.java
index b56fa71..d01afaa 100644
--- a/src/org/unitConverter/math/ExpressionParser.java
+++ b/src/org/unitConverter/math/ExpressionParser.java
@@ -510,8 +510,6 @@ public final class ExpressionParser {
expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1);
}
return expressionRPN;
-
- // TODO document org.unitConverter.expressionParser.ExpressionParser.convertExpressionToPolish(expression)
}
/**
--
cgit v1.2.3
From 4cef115e3fbd228a84ad48eed7af5403e8c8c46e Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sat, 13 Apr 2019 20:01:26 -0400
Subject: Longer prefixes are now favoured over shorter prefixes.
Added 'da-' to the unit file, which was previously missing because it
was interpreted as 'deciatto'. 'D-' can still be used.
---
src/org/unitConverter/UnitsDatabase.java | 98 +++++++++++++-------------------
unitsfile.txt | 1 +
2 files changed, 39 insertions(+), 60 deletions(-)
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 9749e9c..901c6ef 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -67,8 +67,8 @@ public final class UnitsDatabase {
*
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.
- *
Shorter prefixes are preferred to longer prefixes. So, if you have units "BC" and "C", and prefixes "AB" and
- * "A", inputting "ABC" will return the unit "BC" with the prefix "A", not "C" with the prefix "AB".
+ *
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".
*
*
*
@@ -194,6 +194,7 @@ public final class UnitsDatabase {
}
}
+ // create the unit name
final StringBuilder unitNameBuilder = new StringBuilder();
for (final int i : this.prefixCoordinates) {
unitNameBuilder.append(this.prefixNames.get(i));
@@ -256,32 +257,20 @@ public final class UnitsDatabase {
@Override
public Object[] toArray() {
- if (this.map.units.isEmpty())
- // finite, it will work
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray();
- else {
- if (this.map.prefixes.isEmpty())
- // finite, it will work
- return super.toArray();
- else
- // infinite set
- throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
- }
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
}
@Override
public T[] toArray(final T[] a) {
- if (this.map.units.isEmpty())
- // finite, it will work
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray(a);
- else {
- if (this.map.prefixes.isEmpty())
- // finite, it will work
- return super.toArray(a);
- else
- // infinite set
- throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
- }
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
}
}
@@ -437,32 +426,21 @@ public final class UnitsDatabase {
@Override
public Object[] toArray() {
- if (this.map.units.isEmpty())
- // finite, it will work
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray();
- else {
- if (this.map.prefixes.isEmpty())
- // finite, it will work
- return super.toArray();
- else
- // infinite set
- throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
- }
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
+
}
@Override
public T[] toArray(final T[] a) {
- if (this.map.units.isEmpty())
- // finite, it will work
+ if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
return super.toArray(a);
- else {
- if (this.map.prefixes.isEmpty())
- // finite, it will work
- return super.toArray(a);
- else
- // infinite set
- throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
- }
+ else
+ // infinite set
+ throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
}
}
@@ -532,26 +510,26 @@ public final class UnitsDatabase {
throw new IllegalArgumentException("Attempted to test for a unit using a non-string name.");
final String unitName = (String) key;
- // Then, look for the shortest prefix that is attached to a valid unit
- String shortestPrefix = null;
- int shortestLength = Integer.MAX_VALUE;
+ // 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 shorter than the existing largest prefix (since I am looking for the smallest 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() < shortestLength) {
+ if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) {
final String rest = unitName.substring(prefixName.length());
if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
- shortestPrefix = prefixName;
- shortestLength = prefixName.length();
+ longestPrefix = prefixName;
+ longestLength = prefixName.length();
}
}
}
- return shortestPrefix != null;
+ return longestPrefix != null;
}
@Override
@@ -578,34 +556,34 @@ public final class UnitsDatabase {
throw new IllegalArgumentException("Attempted to obtain a unit using a non-string name.");
final String unitName = (String) key;
- // Then, look for the shortest prefix that is attached to a valid unit
- String shortestPrefix = null;
- int shortestLength = Integer.MAX_VALUE;
+ // 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 shorter than the existing largest prefix (since I am looking for the smallest 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() < shortestLength) {
+ if (unitName.startsWith(prefixName) && prefixName.length() > longestLength) {
final String rest = unitName.substring(prefixName.length());
if (this.containsKey(rest) && this.get(rest) instanceof LinearUnit) {
- shortestPrefix = prefixName;
- shortestLength = prefixName.length();
+ longestPrefix = prefixName;
+ longestLength = prefixName.length();
}
}
}
// if none found, returns null
- if (shortestPrefix == null)
+ if (longestPrefix == null)
return null;
else {
// get necessary data
- final String rest = unitName.substring(shortestLength);
+ final String rest = unitName.substring(longestLength);
// 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(shortestPrefix);
+ final UnitPrefix prefix = this.prefixes.get(longestPrefix);
return unit.withPrefix(prefix);
}
diff --git a/unitsfile.txt b/unitsfile.txt
index 14fb6fb..78f8117 100755
--- a/unitsfile.txt
+++ b/unitsfile.txt
@@ -55,6 +55,7 @@ atto- 1e-18
zepto- 1e-21
yocto- 1e-24
+da- deca
D- deca
h- hecto
H- hecto
--
cgit v1.2.3
From 77051c4f70f450a4363be7ae587de36efc1fdd54 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 07:57:38 -0400
Subject: Added more units and dimensions to the converter.
---
.DS_Store | Bin 0 -> 6148 bytes
.classpath | 1 -
dimensionfile.txt | 5 ++-
pom.xml | 15 ++++++-
.../converterGUI/FilterComparator.java | 18 +++++---
.../converterGUI/UnitConverterGUI.java | 50 ++++++++++++---------
unitsfile.txt | 13 ++++++
7 files changed, 70 insertions(+), 32 deletions(-)
create mode 100644 .DS_Store
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..5f8c120
Binary files /dev/null and b/.DS_Store differ
diff --git a/.classpath b/.classpath
index 72d7394..ef141e6 100644
--- a/.classpath
+++ b/.classpath
@@ -7,7 +7,6 @@
-
diff --git a/dimensionfile.txt b/dimensionfile.txt
index 7a1da10..3485de5 100644
--- a/dimensionfile.txt
+++ b/dimensionfile.txt
@@ -4,14 +4,15 @@
# ! 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.
-# I have excluded electric current and quantity since their units are exclusively SI.
+# I have excluded electric current, quantity and luminous intensity since their units are exclusively SI.
LENGTH !
MASS !
TIME !
TEMPERATURE !
-LUMINOUS_INTENSITY !
# Derived Dimensions
+AREA LENGTH^2
+VOLUME LENGTH^3
VELOCITY LENGTH / TIME
ENERGY MASS * VELOCITY^2
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8b3bc0d..5b3e468 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,18 @@
org.unitConverter.converterGUI.UnitConverterGUI
-
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ org.unitConverter.converterGUI.UnitConverterGUI
+
+
+
+
@@ -41,4 +52,4 @@
4.11
-
\ No newline at end of file
+
diff --git a/src/org/unitConverter/converterGUI/FilterComparator.java b/src/org/unitConverter/converterGUI/FilterComparator.java
index bebc2df..ad8d0b0 100755
--- a/src/org/unitConverter/converterGUI/FilterComparator.java
+++ b/src/org/unitConverter/converterGUI/FilterComparator.java
@@ -20,7 +20,7 @@ import java.util.Comparator;
import java.util.Objects;
/**
- * A comparator that compares strings using a filter.
+ * A comparator that compares strings using a filter. It is case-insensitive
*
* @author Adrien Hopkins
* @since 2019-01-15
@@ -72,22 +72,26 @@ public final class FilterComparator implements Comparator {
@Override
public int compare(final String arg0, final String arg1) {
+ // this is case insensitive, so make them lowercase
+ final String arg0lower = arg0.toLowerCase();
+ final String arg1lower = arg1.toLowerCase();
+
// elements that start with the filter always go first
- if (arg0.startsWith(this.filter) && !arg1.startsWith(this.filter))
+ if (arg0lower.startsWith(this.filter) && !arg1lower.startsWith(this.filter))
return -1;
- else if (!arg0.startsWith(this.filter) && arg1.startsWith(this.filter))
+ else if (!arg0lower.startsWith(this.filter) && arg1lower.startsWith(this.filter))
return 1;
// elements that contain the filter but don't start with them go next
- if (arg0.contains(this.filter) && !arg1.contains(this.filter))
+ if (arg0lower.contains(this.filter) && !arg1lower.contains(this.filter))
return -1;
- else if (!arg0.contains(this.filter) && !arg1.contains(this.filter))
+ else if (!arg0lower.contains(this.filter) && !arg1lower.contains(this.filter))
return 1;
// other elements go last
if (this.comparator == null)
- return arg0.compareTo(arg1);
+ return arg0lower.compareTo(arg1lower);
else
- return this.comparator.compare(arg0, arg1);
+ return this.comparator.compare(arg0lower, arg1lower);
}
}
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 49a40d6..34cbef9 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -62,6 +62,35 @@ import org.unitConverter.unit.UnitPrefix;
*/
final class UnitConverterGUI {
private static class Presenter {
+ /**
+ * Adds default units and dimensions to a database.
+ *
+ * @param database
+ * database to add to
+ * @since 2019-04-14
+ */
+ private static void addDefaults(final UnitsDatabase database) {
+ database.addUnit("metre", SI.METRE);
+ database.addUnit("kilogram", SI.KILOGRAM);
+ database.addUnit("gram", SI.KILOGRAM.dividedBy(1000));
+ database.addUnit("second", SI.SECOND);
+ database.addUnit("ampere", SI.AMPERE);
+ database.addUnit("kelvin", SI.KELVIN);
+ database.addUnit("mole", SI.MOLE);
+ database.addUnit("candela", SI.CANDELA);
+ database.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION));
+ database.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY));
+ // nonlinear units - must be loaded manually
+ database.addUnit("tempCelsius", NonlinearUnits.CELSIUS);
+ database.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
+
+ // load initial dimensions
+ database.addDimension("LENGTH", StandardDimensions.LENGTH);
+ database.addDimension("MASS", StandardDimensions.MASS);
+ database.addDimension("TIME", StandardDimensions.TIME);
+ database.addDimension("TEMPERATURE", StandardDimensions.TEMPERATURE);
+ }
+
/** The presenter's associated view. */
private final View view;
@@ -100,26 +129,7 @@ final class UnitConverterGUI {
// load initial units
this.units = new UnitsDatabase();
- this.units.addUnit("metre", SI.METRE);
- this.units.addUnit("kilogram", SI.KILOGRAM);
- this.units.addUnit("gram", SI.KILOGRAM.dividedBy(1000));
- this.units.addUnit("second", SI.SECOND);
- this.units.addUnit("ampere", SI.AMPERE);
- this.units.addUnit("kelvin", SI.KELVIN);
- this.units.addUnit("mole", SI.MOLE);
- this.units.addUnit("candela", SI.CANDELA);
- this.units.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION));
- this.units.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY));
- // nonlinear units - must be loaded manually
- this.units.addUnit("tempCelsius", NonlinearUnits.CELSIUS);
- this.units.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
-
- // load initial dimensions
- this.units.addDimension("LENGTH", StandardDimensions.LENGTH);
- this.units.addDimension("MASS", StandardDimensions.MASS);
- this.units.addDimension("TIME", StandardDimensions.TIME);
- this.units.addDimension("TEMPERATURE", StandardDimensions.TEMPERATURE);
- this.units.addDimension("LUMINOUS_INTENSITY", StandardDimensions.LUMINOUS_INTENSITY);
+ Presenter.addDefaults(this.units);
this.units.loadUnitsFile(new File("unitsfile.txt"));
this.units.loadDimensionFile(new File("dimensionfile.txt"));
diff --git a/unitsfile.txt b/unitsfile.txt
index 78f8117..553fd5e 100755
--- a/unitsfile.txt
+++ b/unitsfile.txt
@@ -124,6 +124,9 @@ T tesla
hertz s^-1
Hz hertz
+gram millikg
+g gram
+
# Angle units and constants
# Tau is the circle constant, equal to a circle's diameter divided by its radius
@@ -243,8 +246,18 @@ calorie 4.18 J
cal calorie
Calorie kilocalorie
Cal Calorie
+Wh W h
# Extra units to only include in the dimension-based converter
+km km
+cm cm
+mm mm
+mg mg
+mL mL
+ml ml
+kJ kJ
+MJ MJ
+kWh kWh
m/s m / s
km/h km / h
ft/s foot / s
--
cgit v1.2.3
From fc1083454e4e9215140802602a17aafeef4515fa Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 14:32:48 -0400
Subject: Added a UnitDatabase test, and fixed some bugs using it.
---
src/org/unitConverter/UnitsDatabase.java | 395 ++++++++++++++++++++-----------
src/test/java/UnitsDatabaseTest.java | 253 ++++++++++++++++++++
2 files changed, 512 insertions(+), 136 deletions(-)
create mode 100644 src/test/java/UnitsDatabaseTest.java
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 901c6ef..959c151 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -30,6 +30,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
@@ -83,6 +84,153 @@ public final class UnitsDatabase {
* @since 2019-04-13
*/
private static final class PrefixedUnitEntrySet extends AbstractSet> {
+ /**
+ * The entry for this set.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ */
+ private static final class PrefixedUnitEntry implements Entry {
+ private final String key;
+ private final Unit value;
+
+ /**
+ * Creates the {@code PrefixedUnitEntry}.
+ *
+ * @param key
+ * @param value
+ * @since 2019-04-14
+ */
+ public PrefixedUnitEntry(final String key, final Unit value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public String getKey() {
+ return this.key;
+ }
+
+ @Override
+ public Unit getValue() {
+ return this.value;
+ }
+
+ @Override
+ public Unit setValue(final Unit value) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * An iterator that iterates over the units of a {@code PrefixedUnitNameSet}.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ */
+ private static final class PrefixedUnitEntryIterator implements Iterator> {
+ // position in the unit list
+ private int unitNamePosition = 0;
+ // the indices of the prefixes attached to the current unit
+ private final List prefixCoordinates = new ArrayList<>();
+
+ private final Map map;
+ private final List unitNames;
+ private final List prefixNames;
+
+ /**
+ * Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
+ *
+ * @since 2019-04-14
+ */
+ public PrefixedUnitEntryIterator(final PrefixedUnitEntrySet set) {
+ this.map = set.map;
+ this.unitNames = new ArrayList<>(set.map.units.keySet());
+ this.prefixNames = new ArrayList<>(set.map.prefixes.keySet());
+ }
+
+ /**
+ * @return current unit name
+ * @since 2019-04-14
+ */
+ private String getCurrentUnitName() {
+ final StringBuilder unitName = new StringBuilder();
+ for (final int i : this.prefixCoordinates) {
+ unitName.append(this.prefixNames.get(i));
+ }
+ unitName.append(this.unitNames.get(this.unitNamePosition));
+
+ return unitName.toString();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (this.unitNames.isEmpty())
+ return false;
+ else {
+ if (this.prefixNames.isEmpty())
+ return this.unitNamePosition >= this.unitNames.size() - 1;
+ else
+ return true;
+ }
+ }
+
+ /**
+ * Changes this iterator's position to the next available one.
+ *
+ * @since 2019-04-14
+ */
+ 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);
+
+ // fix any carrying errors
+ while (i >= 0 && this.prefixCoordinates.get(i) >= this.prefixNames.size()) {
+ // carry over
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public Entry next() {
+ 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
+ if (!this.prefixCoordinates.isEmpty()) {
+ while (this.unitNamePosition < this.unitNames.size()
+ && !(this.map.get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
+ this.unitNames.remove(this.unitNamePosition);
+ }
+ }
+
+ final String nextName = this.getCurrentUnitName();
+
+ this.incrementPosition();
+
+ return new PrefixedUnitEntry(nextName, this.map.get(nextName));
+ }
+ }
+
// the map that created this set
private final PrefixedUnitMap map;
@@ -143,83 +291,7 @@ public final class UnitsDatabase {
@Override
public Iterator> iterator() {
- return new Iterator>() {
- // position in the unit list
- int unitNamePosition = -1;
- // the indices of the prefixes attached to the current unit
- List prefixCoordinates = new ArrayList<>();
-
- List unitNames = new ArrayList<>(PrefixedUnitEntrySet.this.map.units.keySet());
- List prefixNames = new ArrayList<>(PrefixedUnitEntrySet.this.map.prefixes.keySet());
-
- @Override
- public boolean hasNext() {
- if (this.unitNames.isEmpty())
- return false;
- else {
- if (this.prefixNames.isEmpty())
- return this.unitNamePosition >= this.unitNames.size() - 1;
- else
- return true;
- }
- }
-
- @Override
- public Entry next() {
- // increment unit name position
- this.unitNamePosition++;
-
- // if I have prefixes, ensure I'm not using a nonlinear unit
- // since all of the unprefixed stuff is done, just remove nonlinear units
- if (!this.prefixCoordinates.isEmpty()) {
- while (!(PrefixedUnitEntrySet.this.map
- .get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
- this.unitNames.remove(this.unitNamePosition);
- }
- }
-
- // carry over
- if (!this.prefixNames.isEmpty() && this.unitNamePosition >= this.unitNames.size() - 1) {
- // handle prefix position
- this.unitNamePosition = 0;
- int i = this.prefixCoordinates.size() - 1;
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
-
- while (this.prefixCoordinates.get(i) >= this.prefixNames.size() - 1) {
- this.prefixCoordinates.set(i, 0);
- i--;
- if (i < 0) {
- this.prefixCoordinates.add(0, 0);
- }
- }
- }
-
- // create the unit name
- final StringBuilder unitNameBuilder = new StringBuilder();
- for (final int i : this.prefixCoordinates) {
- unitNameBuilder.append(this.prefixNames.get(i));
- }
- unitNameBuilder.append(this.unitNames.get(this.unitNamePosition));
-
- final String unitName = unitNameBuilder.toString();
- return new Entry() {
- @Override
- public String getKey() {
- return unitName;
- }
-
- @Override
- public Unit getValue() {
- return PrefixedUnitEntrySet.this.map.get(unitName);
- }
-
- @Override
- public Unit setValue(final Unit value) {
- throw new UnsupportedOperationException();
- }
- };
- }
- };
+ return new PrefixedUnitEntryIterator(this);
}
@Override
@@ -282,6 +354,115 @@ public final class UnitsDatabase {
* @since 2019-04-13
*/
private static final class PrefixedUnitNameSet extends AbstractSet {
+ /**
+ * An iterator that iterates over the units of a {@code PrefixedUnitNameSet}.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ */
+ private static final class PrefixedUnitNameIterator implements Iterator {
+ // position in the unit list
+ private int unitNamePosition = 0;
+ // the indices of the prefixes attached to the current unit
+ private final List prefixCoordinates = new ArrayList<>();
+
+ private final Map map;
+ private final List unitNames;
+ private final List prefixNames;
+
+ /**
+ * Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
+ *
+ * @since 2019-04-14
+ */
+ public PrefixedUnitNameIterator(final PrefixedUnitNameSet set) {
+ this.map = set.map;
+ this.unitNames = new ArrayList<>(set.map.units.keySet());
+ this.prefixNames = new ArrayList<>(set.map.prefixes.keySet());
+ }
+
+ /**
+ * @return current unit name
+ * @since 2019-04-14
+ */
+ private String getCurrentUnitName() {
+ final StringBuilder unitName = new StringBuilder();
+ for (final int i : this.prefixCoordinates) {
+ unitName.append(this.prefixNames.get(i));
+ }
+ unitName.append(this.unitNames.get(this.unitNamePosition));
+
+ return unitName.toString();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (this.unitNames.isEmpty())
+ return false;
+ else {
+ if (this.prefixNames.isEmpty())
+ return this.unitNamePosition >= this.unitNames.size() - 1;
+ else
+ return true;
+ }
+ }
+
+ /**
+ * Changes this iterator's position to the next available one.
+ *
+ * @since 2019-04-14
+ */
+ 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);
+
+ // fix any carrying errors
+ while (i >= 0 && this.prefixCoordinates.get(i) >= this.prefixNames.size()) {
+ // carry over
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public String next() {
+ 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
+ if (!this.prefixCoordinates.isEmpty()) {
+ while (this.unitNamePosition < this.unitNames.size()
+ && !(this.map.get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
+ this.unitNames.remove(this.unitNamePosition);
+ }
+ }
+
+ final String nextName = this.getCurrentUnitName();
+
+ this.incrementPosition();
+
+ return nextName;
+ }
+ }
+
// the map that created this set
private final PrefixedUnitMap map;
@@ -330,65 +511,7 @@ public final class UnitsDatabase {
@Override
public Iterator iterator() {
- return new Iterator() {
- // position in the unit list
- int unitNamePosition = -1;
- // the indices of the prefixes attached to the current unit
- List prefixCoordinates = new ArrayList<>();
-
- List unitNames = new ArrayList<>(PrefixedUnitNameSet.this.map.units.keySet());
- List prefixNames = new ArrayList<>(PrefixedUnitNameSet.this.map.prefixes.keySet());
-
- @Override
- public boolean hasNext() {
- if (this.unitNames.isEmpty())
- return false;
- else {
- if (this.prefixNames.isEmpty())
- return this.unitNamePosition >= this.unitNames.size() - 1;
- else
- return true;
- }
- }
-
- @Override
- public String next() {
- // increment unit name position
- this.unitNamePosition++;
-
- // if I have prefixes, ensure I'm not using a nonlinear unit
- // since all of the unprefixed stuff is done, just remove nonlinear units
- if (!this.prefixCoordinates.isEmpty()) {
- while (!(PrefixedUnitNameSet.this.map
- .get(this.unitNames.get(this.unitNamePosition)) instanceof LinearUnit)) {
- this.unitNames.remove(this.unitNamePosition);
- }
- }
-
- // carry over
- if (!this.prefixNames.isEmpty() && this.unitNamePosition >= this.unitNames.size() - 1) {
- // handle prefix position
- this.unitNamePosition = 0;
- int i = this.prefixCoordinates.size() - 1;
- this.prefixCoordinates.set(i, this.prefixCoordinates.get(i) + 1);
-
- while (this.prefixCoordinates.get(i) >= this.prefixNames.size() - 1) {
- this.prefixCoordinates.set(i, 0);
- i--;
- if (i < 0) {
- this.prefixCoordinates.add(0, 0);
- }
- }
- }
-
- final StringBuilder unitName = new StringBuilder();
- for (final int i : this.prefixCoordinates) {
- unitName.append(this.prefixNames.get(i));
- }
- unitName.append(this.unitNames.get(this.unitNamePosition));
- return unitName.toString();
- }
- };
+ return new PrefixedUnitNameIterator(this);
}
@Override
diff --git a/src/test/java/UnitsDatabaseTest.java b/src/test/java/UnitsDatabaseTest.java
new file mode 100644
index 0000000..39f95a5
--- /dev/null
+++ b/src/test/java/UnitsDatabaseTest.java
@@ -0,0 +1,253 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package test.java;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.junit.Test;
+import org.unitConverter.UnitsDatabase;
+import org.unitConverter.unit.AbstractUnit;
+import org.unitConverter.unit.DefaultUnitPrefix;
+import org.unitConverter.unit.LinearUnit;
+import org.unitConverter.unit.SI;
+import org.unitConverter.unit.Unit;
+import org.unitConverter.unit.UnitPrefix;
+
+/**
+ * A test for the {@link UnitsDatabase} class.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-14
+ */
+public class UnitsDatabaseTest {
+ // some linear units and one nonlinear
+ private static final Unit U = SI.METRE;
+ private static final Unit V = SI.KILOGRAM;
+ private static final Unit W = SI.SECOND;
+
+ // used for testing expressions
+ // J = U^2 * V / W^2
+ private static final LinearUnit J = SI.KILOGRAM.times(SI.METRE.toExponent(2)).dividedBy(SI.SECOND.toExponent(2));
+ private static final Unit NONLINEAR = new AbstractUnit(SI.METRE) {
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value + 1;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value - 1;
+ }
+ };
+
+ // make the prefix values prime so I can tell which multiplications were made
+ private static final UnitPrefix A = new DefaultUnitPrefix(2);
+ private static final UnitPrefix B = new DefaultUnitPrefix(3);
+ private static final UnitPrefix C = new DefaultUnitPrefix(5);
+ private static final UnitPrefix AB = new DefaultUnitPrefix(7);
+ private static final UnitPrefix BC = new DefaultUnitPrefix(11);
+
+ /**
+ * Test that prefixes correctly apply to units.
+ *
+ * @since 2019-04-14
+ */
+ @Test
+ public void testPrefixes() {
+ final UnitsDatabase database = new UnitsDatabase();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ // get the product
+ final Unit abcuNonlinear = database.getUnit("ABCU");
+ assert abcuNonlinear instanceof LinearUnit;
+
+ final LinearUnit abcu = (LinearUnit) abcuNonlinear;
+ assertEquals(A.getMultiplier() * B.getMultiplier() * C.getMultiplier(), abcu.getConversionFactor(), 1e-15);
+ }
+
+ /**
+ * Tests the functionnalites of the prefixless unit map.
+ *
+ *
+ * The map should be an auto-updating view of the units in the database.
+ *
+ *
+ * @since 2019-04-14
+ */
+ @Test
+ public void testPrefixlessUnitMap() {
+ final UnitsDatabase database = new UnitsDatabase();
+ final Map prefixlessUnits = database.unitMapPrefixless();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+
+ // this should work because the map should be an auto-updating view
+ assertTrue(prefixlessUnits.containsKey("U"));
+ assertFalse(prefixlessUnits.containsKey("Z"));
+
+ assertTrue(prefixlessUnits.containsValue(U));
+ assertFalse(prefixlessUnits.containsValue(NONLINEAR));
+ }
+
+ /**
+ * Tests that the database correctly stores and retrieves units, ignoring prefixes.
+ *
+ * @since 2019-04-14
+ */
+ @Test
+ public void testPrefixlessUnits() {
+ final UnitsDatabase database = new UnitsDatabase();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+
+ assertTrue(database.containsUnitName("U"));
+ assertFalse(database.containsUnitName("Z"));
+
+ assertEquals(U, database.getUnit("U"));
+ assertEquals(null, database.getUnit("Z"));
+ }
+
+ /**
+ * Test that unit expressions return the correct value.
+ *
+ * @since 2019-04-14
+ */
+ @Test
+ public void testUnitExpressions() {
+ // load units
+ final UnitsDatabase database = new UnitsDatabase();
+
+ database.addUnit("U", U);
+ database.addUnit("V", V);
+ database.addUnit("W", W);
+ database.addUnit("fj", J.times(5));
+ database.addUnit("ej", J.times(8));
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ // first test - test prefixes and operations
+ final Unit expected1 = J.withPrefix(A).withPrefix(B).withPrefix(C).withPrefix(C);
+ final Unit actual1 = database.getUnitFromExpression("ABV * CU^2 / W / W");
+
+ assertEquals(expected1, actual1);
+
+ // second test - test addition and subtraction
+ final Unit expected2 = J.times(58);
+ final Unit actual2 = database.getUnitFromExpression("2 fj + 6 ej");
+
+ assertEquals(expected2, actual2);
+ }
+
+ /**
+ * Tests both the unit name iterator and the name-unit entry iterator
+ *
+ * @since 2019-04-14
+ */
+ @Test
+ public void testUnitIterator() {
+ // load units
+ final UnitsDatabase database = new UnitsDatabase();
+
+ database.addUnit("J", J);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+
+ final Iterator nameIterator = database.unitMap().keySet().iterator();
+ final Iterator> entryIterator = database.unitMap().entrySet().iterator();
+
+ int expectedLength = 1;
+ int unitsWithThisLengthSoFar = 0;
+
+ // loop 1000 times
+ for (int i = 0; i < 1000; i++) {
+ // expected length of next
+ if (unitsWithThisLengthSoFar >= (int) Math.pow(3, expectedLength - 1)) {
+ expectedLength++;
+ unitsWithThisLengthSoFar = 0;
+ }
+
+ final String nextName = nameIterator.next();
+ final Unit nextUnit = database.getUnit(nextName);
+ final Entry nextEntry = entryIterator.next();
+
+ assertEquals(expectedLength, nextName.length());
+ assertEquals(nextName, nextEntry.getKey());
+ assertEquals(nextUnit, nextEntry.getValue());
+
+ unitsWithThisLengthSoFar++;
+ }
+ }
+
+ /**
+ * Determine, given a unit name that could mean multiple things, which meaning is chosen.
+ *
+ * For example, "ABCU" could mean "A-B-C-U", "AB-C-U", or "A-BC-U". In this case, "AB-C-U" is the correct choice.
+ *
+ *
+ * @since 2019-04-14
+ */
+ @Test
+ public void testUnitPrefixCombinations() {
+ // load units
+ final UnitsDatabase database = new UnitsDatabase();
+
+ database.addUnit("J", J);
+
+ database.addPrefix("A", A);
+ database.addPrefix("B", B);
+ database.addPrefix("C", C);
+ database.addPrefix("AB", AB);
+ database.addPrefix("BC", BC);
+
+ // test 1 - AB-C-J vs A-BC-J vs A-B-C-J
+ final Unit expected1 = J.withPrefix(AB).withPrefix(C);
+ final Unit actual1 = database.getUnit("ABCJ");
+
+ assertEquals(expected1, actual1);
+
+ // test 2 - ABC-J vs AB-CJ vs AB-C-J
+ database.addUnit("CJ", J.times(13));
+ database.addPrefix("ABC", new DefaultUnitPrefix(17));
+
+ final Unit expected2 = J.times(17);
+ final Unit actual2 = database.getUnit("ABCJ");
+
+ assertEquals(expected2, actual2);
+ }
+}
--
cgit v1.2.3
From 54b9f00faba367e1ef325c9d0bc75a848fadb906 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 15:39:38 -0400
Subject: The unit and prefix viewers now use SearchBoxList.
---
src/org/unitConverter/UnitsDatabase.java | 2 +
.../converterGUI/FilterComparator.java | 50 ++-
.../unitConverter/converterGUI/SearchBoxList.java | 77 +++-
.../converterGUI/UnitConverterGUI.java | 401 +++++++--------------
unitsfile.txt | 2 +-
5 files changed, 247 insertions(+), 285 deletions(-)
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 959c151..abe6546 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -134,6 +134,7 @@ public final class UnitsDatabase {
// the indices of the prefixes attached to the current unit
private final List prefixCoordinates = new ArrayList<>();
+ // values from the unit entry set
private final Map map;
private final List unitNames;
private final List prefixNames;
@@ -366,6 +367,7 @@ public final class UnitsDatabase {
// the indices of the prefixes attached to the current unit
private final List prefixCoordinates = new ArrayList<>();
+ // values from the unit name set
private final Map map;
private final List unitNames;
private final List prefixNames;
diff --git a/src/org/unitConverter/converterGUI/FilterComparator.java b/src/org/unitConverter/converterGUI/FilterComparator.java
index ad8d0b0..ef94602 100755
--- a/src/org/unitConverter/converterGUI/FilterComparator.java
+++ b/src/org/unitConverter/converterGUI/FilterComparator.java
@@ -41,6 +41,12 @@ public final class FilterComparator implements Comparator {
* @since v0.1.0
*/
private final Comparator comparator;
+ /**
+ * Whether or not the comparison is case-sensitive.
+ *
+ * @since 2019-04-14
+ */
+ private final boolean caseSensitive;
/**
* Creates the {@code FilterComparator}.
@@ -60,38 +66,62 @@ public final class FilterComparator implements Comparator {
* string to filter by
* @param comparator
* comparator to fall back to if all else fails, null is compareTo.
+ * @throws NullPointerException
+ * if filter is null
* @since 2019-01-15
* @since v0.1.0
+ */
+ public FilterComparator(final String filter, final Comparator comparator) {
+ this(filter, comparator, false);
+ }
+
+ /**
+ * Creates the {@code FilterComparator}.
+ *
+ * @param filter
+ * string to filter by
+ * @param comparator
+ * comparator to fall back to if all else fails, null is compareTo.
+ * @param caseSensitive
+ * whether or not the comparator is case-sensitive
* @throws NullPointerException
* if filter is null
+ * @since 2019-04-14
*/
- public FilterComparator(final String filter, final Comparator comparator) {
+ public FilterComparator(final String filter, final Comparator comparator, final boolean caseSensitive) {
this.filter = Objects.requireNonNull(filter, "filter must not be null.");
this.comparator = comparator;
+ this.caseSensitive = caseSensitive;
}
@Override
public int compare(final String arg0, final String arg1) {
- // this is case insensitive, so make them lowercase
- final String arg0lower = arg0.toLowerCase();
- final String arg1lower = arg1.toLowerCase();
+ // if this is case insensitive, make them lowercase
+ final String str0, str1;
+ if (this.caseSensitive) {
+ str0 = arg0;
+ str1 = arg1;
+ } else {
+ str0 = arg0.toLowerCase();
+ str1 = arg1.toLowerCase();
+ }
// elements that start with the filter always go first
- if (arg0lower.startsWith(this.filter) && !arg1lower.startsWith(this.filter))
+ if (str0.startsWith(this.filter) && !str1.startsWith(this.filter))
return -1;
- else if (!arg0lower.startsWith(this.filter) && arg1lower.startsWith(this.filter))
+ else if (!str0.startsWith(this.filter) && str1.startsWith(this.filter))
return 1;
// elements that contain the filter but don't start with them go next
- if (arg0lower.contains(this.filter) && !arg1lower.contains(this.filter))
+ if (str0.contains(this.filter) && !str1.contains(this.filter))
return -1;
- else if (!arg0lower.contains(this.filter) && !arg1lower.contains(this.filter))
+ else if (!str0.contains(this.filter) && !str1.contains(this.filter))
return 1;
// other elements go last
if (this.comparator == null)
- return arg0lower.compareTo(arg1lower);
+ return str0.compareTo(str1);
else
- return this.comparator.compare(arg0lower, arg1lower);
+ return this.comparator.compare(str0, str1);
}
}
diff --git a/src/org/unitConverter/converterGUI/SearchBoxList.java b/src/org/unitConverter/converterGUI/SearchBoxList.java
index 7d3b748..35cc347 100644
--- a/src/org/unitConverter/converterGUI/SearchBoxList.java
+++ b/src/org/unitConverter/converterGUI/SearchBoxList.java
@@ -22,6 +22,7 @@ import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
import java.util.function.Predicate;
import javax.swing.JList;
@@ -61,16 +62,25 @@ final class SearchBoxList extends JPanel {
// event.
private boolean searchBoxFocused = false;
- private Predicate searchFilter = o -> true;
+ private Predicate customSearchFilter = o -> true;
+ private final Comparator defaultOrdering;
+ private final boolean caseSensitive;
+
+ public SearchBoxList(final Collection itemsToFilter) {
+ this(itemsToFilter, null, false);
+ }
/**
* Creates the {@code SearchBoxList}.
*
* @since 2019-04-13
*/
- public SearchBoxList(final Collection itemsToFilter) {
+ public SearchBoxList(final Collection itemsToFilter, final Comparator defaultOrdering,
+ final boolean caseSensitive) {
super(new BorderLayout(), true);
this.itemsToFilter = itemsToFilter;
+ this.defaultOrdering = defaultOrdering;
+ this.caseSensitive = caseSensitive;
// create the components
this.listModel = new DelegateListModel<>(new ArrayList<>(itemsToFilter));
@@ -108,7 +118,7 @@ final class SearchBoxList extends JPanel {
* @since 2019-04-13
*/
public void addSearchFilter(final Predicate filter) {
- this.searchFilter = this.searchFilter.and(filter);
+ this.customSearchFilter = this.customSearchFilter.and(filter);
}
/**
@@ -117,7 +127,44 @@ final class SearchBoxList extends JPanel {
* @since 2019-04-13
*/
public void clearSearchFilters() {
- this.searchFilter = o -> true;
+ this.customSearchFilter = o -> true;
+ }
+
+ /**
+ * @return this component's search box component
+ * @since 2019-04-14
+ */
+ public final JTextField getSearchBox() {
+ return this.searchBox;
+ }
+
+ /**
+ * @param searchText
+ * text to search for
+ * @return a filter that filters out that text, based on this list's case sensitive setting
+ * @since 2019-04-14
+ */
+ private Predicate getSearchFilter(final String searchText) {
+ if (this.caseSensitive)
+ return string -> string.contains(searchText);
+ else
+ return string -> string.toLowerCase().contains(searchText.toLowerCase());
+ }
+
+ /**
+ * @return this component's list component
+ * @since 2019-04-14
+ */
+ public final JList getSearchList() {
+ return this.searchItems;
+ }
+
+ /**
+ * @return index selected in item list
+ * @since 2019-04-14
+ */
+ public int getSelectedIndex() {
+ return this.searchItems.getSelectedIndex();
}
/**
@@ -135,17 +182,18 @@ final class SearchBoxList extends JPanel {
*/
public void reapplyFilter() {
final String searchText = this.searchBoxEmpty ? "" : this.searchBox.getText();
- final FilterComparator comparator = new FilterComparator(searchText);
+ final FilterComparator comparator = new FilterComparator(searchText, this.defaultOrdering, this.caseSensitive);
+ final Predicate searchFilter = this.getSearchFilter(searchText);
this.listModel.clear();
this.itemsToFilter.forEach(string -> {
- if (string.toLowerCase().contains(searchText.toLowerCase())) {
+ if (searchFilter.test(string)) {
this.listModel.add(string);
}
});
// applies the custom filters
- this.listModel.removeIf(this.searchFilter.negate());
+ this.listModel.removeIf(this.customSearchFilter.negate());
// sorts the remaining items
this.listModel.sort(comparator);
@@ -181,23 +229,32 @@ final class SearchBoxList extends JPanel {
}
}
+ /**
+ * Runs whenever the text in the search box is changed.
+ *
+ * Reapplies the search filter, and custom filters.
+ *
+ *
+ * @since 2019-04-14
+ */
private void searchBoxTextChanged() {
if (this.searchBoxFocused) {
this.searchBoxEmpty = this.searchBox.getText().equals("");
}
final String searchText = this.searchBoxEmpty ? "" : this.searchBox.getText();
- final FilterComparator comparator = new FilterComparator(searchText);
+ final FilterComparator comparator = new FilterComparator(searchText, this.defaultOrdering, this.caseSensitive);
+ final Predicate searchFilter = this.getSearchFilter(searchText);
// initialize list with items that match the filter then sort
this.listModel.clear();
this.itemsToFilter.forEach(string -> {
- if (string.toLowerCase().contains(searchText.toLowerCase())) {
+ if (searchFilter.test(string)) {
this.listModel.add(string);
}
});
// applies the custom filters
- this.listModel.removeIf(this.searchFilter.negate());
+ this.listModel.removeIf(this.customSearchFilter.negate());
// sorts the remaining items
this.listModel.sort(comparator);
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 34cbef9..cacc3b7 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -35,16 +35,12 @@ import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
-import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
-import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
-import javax.swing.ListModel;
-import javax.swing.ListSelectionModel;
import org.unitConverter.UnitsDatabase;
import org.unitConverter.dimension.StandardDimensions;
@@ -95,20 +91,14 @@ final class UnitConverterGUI {
private final View view;
/** The units known by the program. */
- private final UnitsDatabase units;
+ private final UnitsDatabase database;
/** The names of all of the units */
private final List unitNames;
- /** The names of all of the units, but filtered */
- private final DelegateListModel unitNamesFiltered;
-
/** The names of all of the prefixes */
private final List prefixNames;
- /** The names of all of the prefixes */
- private final DelegateListModel prefixNamesFiltered;
-
/** The names of all of the dimensions */
private final List dimensionNames;
@@ -128,23 +118,23 @@ final class UnitConverterGUI {
this.view = view;
// load initial units
- this.units = new UnitsDatabase();
- Presenter.addDefaults(this.units);
+ this.database = new UnitsDatabase();
+ Presenter.addDefaults(this.database);
- this.units.loadUnitsFile(new File("unitsfile.txt"));
- this.units.loadDimensionFile(new File("dimensionfile.txt"));
+ 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
this.prefixNameComparator = (o1, o2) -> {
- if (!Presenter.this.units.containsPrefixName(o1))
+ if (!Presenter.this.database.containsPrefixName(o1))
return -1;
- else if (!Presenter.this.units.containsPrefixName(o2))
+ else if (!Presenter.this.database.containsPrefixName(o2))
return 1;
- final UnitPrefix p1 = Presenter.this.units.getPrefix(o1);
- final UnitPrefix p2 = Presenter.this.units.getPrefix(o2);
+ final UnitPrefix p1 = Presenter.this.database.getPrefix(o1);
+ final UnitPrefix p2 = Presenter.this.database.getPrefix(o2);
if (p1.getMultiplier() < p2.getMultiplier())
return -1;
@@ -154,19 +144,13 @@ final class UnitConverterGUI {
return o1.compareTo(o2);
};
- this.unitNames = new ArrayList<>(this.units.unitMapPrefixless().keySet());
+ this.unitNames = new ArrayList<>(this.database.unitMapPrefixless().keySet());
this.unitNames.sort(null); // sorts it using Comparable
- this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.unitMapPrefixless().keySet()));
- this.unitNamesFiltered.sort(null); // sorts it using Comparable
-
- this.prefixNames = new ArrayList<>(this.units.prefixMap().keySet());
+ this.prefixNames = new ArrayList<>(this.database.prefixMap().keySet());
this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator
- this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixMap().keySet()));
- this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator
-
- this.dimensionNames = new DelegateListModel<>(new ArrayList<>(this.units.dimensionMap().keySet()));
+ 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
@@ -174,9 +158,43 @@ final class UnitConverterGUI {
// print out unit counts
System.out.printf("Successfully loaded %d units with %d unit names (%d base units).%n",
- new HashSet<>(this.units.unitMapPrefixless().values()).size(),
- this.units.unitMapPrefixless().size(),
- new HashSet<>(this.units.unitMapPrefixless().values()).stream().filter(isFullBase).count());
+ new HashSet<>(this.database.unitMapPrefixless().values()).size(),
+ this.database.unitMapPrefixless().size(),
+ new HashSet<>(this.database.unitMapPrefixless().values()).stream().filter(isFullBase).count());
+ }
+
+ /**
+ * Converts in the dimension-based converter
+ *
+ * @since 2019-04-13
+ */
+ public final void convertDimensionBased() {
+ final String fromSelection = this.view.getFromSelection();
+ if (fromSelection == null) {
+ this.view.showErrorDialog("Error", "No unit selected in From field");
+ return;
+ }
+ final String toSelection = this.view.getToSelection();
+ if (toSelection == null) {
+ 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 String input = this.view.getDimensionConverterInput();
+ if (input.equals("")) {
+ this.view.showErrorDialog("Error", "No value to convert entered.");
+ return;
+ }
+ final double beforeValue = Double.parseDouble(input);
+ final double value = to.convertFromBase(from.convertToBase(beforeValue));
+
+ final String output = this.getRoundedString(value);
+
+ this.view.setDimensionConverterOutputText(
+ String.format("%s %s = %s %s", input, fromSelection, output, toSelection));
}
/**
@@ -190,7 +208,7 @@ final class UnitConverterGUI {
* @since 2019-01-26
* @since v0.1.0
*/
- public final void convert() {
+ public final void convertExpressions() {
final String fromUnitString = this.view.getFromText();
final String toUnitString = this.view.getToText();
@@ -202,7 +220,7 @@ final class UnitConverterGUI {
// try to parse from
final Unit from;
try {
- from = this.units.getUnitFromExpression(fromUnitString);
+ from = this.database.getUnitFromExpression(fromUnitString);
} catch (final IllegalArgumentException e) {
this.view.showErrorDialog("Parse Error", "Could not recognize text in From entry: " + e.getMessage());
return;
@@ -212,11 +230,11 @@ final class UnitConverterGUI {
// try to parse to
final Unit to;
try {
- if (this.units.containsUnitName(toUnitString)) {
+ if (this.database.containsUnitName(toUnitString)) {
// if it's a unit, convert to that
- to = this.units.getUnit(toUnitString);
+ to = this.database.getUnit(toUnitString);
} else {
- to = this.units.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());
@@ -233,50 +251,35 @@ final class UnitConverterGUI {
value = to.convertFromBase(from.convertToBase(1));
// round value
- final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures));
- String output = bigValue.toString();
+ final String output = this.getRoundedString(value);
- // 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);
- }
- }
-
- this.view.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString));
+ this.view.setExpressionConverterOutputText(
+ String.format("%s = %s %s", fromUnitString, output, toUnitString));
}
/**
- * Converts in the dimension-based converter
- *
+ * @return a list of all of the unit dimensions
* @since 2019-04-13
*/
- public final void convertDimensionBased() {
- final String fromSelection = this.view.getFromSelection();
- if (fromSelection == null) {
- this.view.showErrorDialog("Error", "No unit selected in From field");
- return;
- }
- final String toSelection = this.view.getToSelection();
- if (toSelection == null) {
- this.view.showErrorDialog("Error", "No unit selected in To field");
- return;
- }
-
- final Unit from = this.units.getUnit(fromSelection);
- final Unit to = this.units.getUnit(toSelection);
+ public final List dimensionNameList() {
+ return this.dimensionNames;
+ }
- final String input = this.view.getDimensionBasedInput();
- if (input.equals("")) {
- this.view.showErrorDialog("Error", "No value to convert entered.");
- return;
- }
- final double beforeValue = Double.parseDouble(input);
- final double value = to.convertFromBase(from.convertToBase(beforeValue));
+ /**
+ * @return a comparator to compare prefix names
+ * @since 2019-04-14
+ */
+ public final Comparator getPrefixNameComparator() {
+ return this.prefixNameComparator;
+ }
+ /**
+ * @param value
+ * value to round
+ * @return string of that value rounded to {@code significantDigits} significant digits.
+ * @since 2019-04-14
+ */
+ private final String getRoundedString(final double value) {
// round value
final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures));
String output = bigValue.toString();
@@ -291,86 +294,15 @@ final class UnitConverterGUI {
}
}
- this.view.setDimensionBasedOutputText(
- String.format("%s %s = %s %s", input, fromSelection, output, toSelection));
- }
-
- /**
- * @return a list of all of the unit dimensions
- * @since 2019-04-13
- */
- public final List dimensionNameList() {
- return this.dimensionNames;
- }
-
- /**
- * Filters the filtered model for units
- *
- * @param filter
- * filter to use
- * @since 2019-01-15
- * @since v0.1.0
- */
- private final void filterFilteredPrefixModel(final Predicate filter) {
- this.prefixNamesFiltered.clear();
- for (final String prefixName : this.prefixNames) {
- if (filter.test(prefixName)) {
- this.prefixNamesFiltered.add(prefixName);
- }
- }
- }
-
- /**
- * Filters the filtered model for units
- *
- * @param filter
- * filter to use
- * @since 2019-01-15
- * @since v0.1.0
- */
- private final void filterFilteredUnitModel(final Predicate filter) {
- this.unitNamesFiltered.clear();
- for (final String unitName : this.unitNames) {
- if (filter.test(unitName)) {
- this.unitNamesFiltered.add(unitName);
- }
- }
+ return output;
}
/**
- * @return a list model of all of the unit keys
- * @since 2019-01-14
- * @since v0.1.0
- */
- public final ListModel keyListModel() {
- return this.unitNamesFiltered;
- }
-
- /**
- * Runs whenever the prefix filter is changed.
- *
- * Filters the prefix list then sorts it using a {@code FilterComparator}.
- *
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public final void prefixFilterUpdated() {
- final String filter = this.view.getPrefixFilterText();
- if (filter.equals("")) {
- this.filterFilteredPrefixModel(t -> true);
- } else {
- this.filterFilteredPrefixModel(t -> t.contains(filter));
- }
- this.prefixNamesFiltered.sort(new FilterComparator(filter));
- }
-
- /**
- * @return a list model of all of the prefix names
- * @since 2019-01-15
+ * @return a set of all prefix names in the database
+ * @since 2019-04-14
*/
- public final ListModel prefixNameListModel() {
- return this.prefixNamesFiltered;
+ public final Set prefixNameSet() {
+ return this.database.prefixMap().keySet();
}
/**
@@ -383,12 +315,11 @@ final class UnitConverterGUI {
* @since v0.1.0
*/
public final void prefixSelected() {
- final int index = this.view.getPrefixListSelection();
- if (index == -1)
+ final String prefixName = this.view.getPrefixViewerSelection();
+ if (prefixName == null)
return;
else {
- final String prefixName = this.prefixNamesFiltered.get(index);
- final UnitPrefix prefix = this.units.getPrefix(prefixName);
+ final UnitPrefix prefix = this.database.getPrefix(prefixName);
this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier()));
}
@@ -403,25 +334,6 @@ final class UnitConverterGUI {
this.significantFigures = significantFigures;
}
- /**
- * Runs whenever the unit filter is changed.
- *
- * Filters the unit list then sorts it using a {@code FilterComparator}.
- *
- *
- * @since 2019-01-15
- * @since v0.1.0
- */
- public final void unitFilterUpdated() {
- final String filter = this.view.getUnitFilterText();
- if (filter.equals("")) {
- this.filterFilteredUnitModel(t -> true);
- } else {
- this.filterFilteredUnitModel(t -> t.contains(filter));
- }
- this.unitNamesFiltered.sort(new FilterComparator(filter));
- }
-
/**
* Returns true if and only if the unit represented by {@code unitName} has the dimension represented by
* {@code dimensionName}.
@@ -433,9 +345,9 @@ final class UnitConverterGUI {
* @return whether unit has dimenision
* @since 2019-04-13
*/
- public boolean unitMatchesDimension(final String unitName, final String dimensionName) {
- final Unit unit = this.units.getUnit(unitName);
- final UnitDimension dimension = this.units.getDimension(dimensionName);
+ public final boolean unitMatchesDimension(final String unitName, final String dimensionName) {
+ final Unit unit = this.database.getUnit(unitName);
+ final UnitDimension dimension = this.database.getDimension(dimensionName);
return unit.getDimension().equals(dimension);
}
@@ -448,20 +360,23 @@ final class UnitConverterGUI {
* @since 2019-01-15
* @since v0.1.0
*/
- public void unitNameSelected() {
- final int index = this.view.getUnitListSelection();
- if (index == -1)
+ public final void unitNameSelected() {
+ final String unitName = this.view.getUnitViewerSelection();
+ if (unitName == null)
return;
else {
- final String unitName = this.unitNamesFiltered.get(index);
- final Unit unit = this.units.getUnit(unitName);
+ 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
+ */
public final Set unitNameSet() {
- return this.units.unitMapPrefixless().keySet();
+ return this.database.unitMapPrefixless().keySet();
}
}
@@ -471,26 +386,17 @@ final class UnitConverterGUI {
/** The view's associated presenter. */
private final Presenter presenter;
- /** The list of unit names in the unit viewer */
- private final JList unitNameList;
- /** The list of prefix names in the prefix viewer */
- private final JList prefixNameList;
- /** The unit search box in the unit viewer */
- private final JTextField unitFilterEntry;
- /** The text box for unit data in the unit viewer */
- private final JTextArea unitTextBox;
- /** The prefix search box in the prefix viewer */
- private final JTextField prefixFilterEntry;
- /** The text box for prefix data in the prefix viewer */
- private final JTextArea prefixTextBox;
+ // DIMENSION-BASED CONVERTER
+ /** The panel for inputting values in the dimension-based converter */
+ private final JTextField valueInput;
/** The panel for "From" in the dimension-based converter */
private final SearchBoxList fromSearch;
/** The panel for "To" in the dimension-based converter */
private final SearchBoxList toSearch;
- /** The panel for inputting values in the dimension-based converter */
- private final JTextField valueInput;
/** 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;
/** The "To" entry in the conversion panel */
@@ -498,6 +404,16 @@ final class UnitConverterGUI {
/** 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;
+ /** The searchable list of prefix names in the prefix viewer */
+ private final SearchBoxList prefixNameList;
+ /** The text box for unit data in the unit viewer */
+ private final JTextArea unitTextBox;
+ /** The text box for prefix data in the prefix viewer */
+ private final JTextArea prefixTextBox;
+
/**
* Creates the {@code View}.
*
@@ -510,11 +426,10 @@ final class UnitConverterGUI {
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// create the components
- this.unitNameList = new JList<>(this.presenter.keyListModel());
- this.prefixNameList = new JList<>(this.presenter.prefixNameListModel());
- this.unitFilterEntry = new JTextField();
+ this.unitNameList = new SearchBoxList(this.presenter.unitNameSet());
+ this.prefixNameList = new SearchBoxList(this.presenter.prefixNameSet(),
+ this.presenter.getPrefixNameComparator(), true);
this.unitTextBox = new JTextArea();
- this.prefixFilterEntry = new JTextField();
this.prefixTextBox = new JTextArea();
this.fromSearch = new SearchBoxList(this.presenter.unitNameSet());
this.toSearch = new SearchBoxList(this.presenter.unitNameSet());
@@ -534,7 +449,7 @@ final class UnitConverterGUI {
* @return value in dimension-based converter
* @since 2019-04-13
*/
- public String getDimensionBasedInput() {
+ public String getDimensionConverterInput() {
return this.valueInput.getText();
}
@@ -556,21 +471,12 @@ final class UnitConverterGUI {
}
/**
- * @return text in prefix filter
+ * @return index of selected prefix in prefix viewer
* @since 2019-01-15
* @since v0.1.0
*/
- public String getPrefixFilterText() {
- return this.prefixFilterEntry.getText();
- }
-
- /**
- * @return index of selected prefix
- * @since 2019-01-15
- * @since v0.1.0
- */
- public int getPrefixListSelection() {
- return this.prefixNameList.getSelectedIndex();
+ public String getPrefixViewerSelection() {
+ return this.prefixNameList.getSelectedValue();
}
/**
@@ -591,20 +497,12 @@ final class UnitConverterGUI {
}
/**
- * @return text in unit filter
- * @see javax.swing.text.JTextComponent#getText()
- */
- public String getUnitFilterText() {
- return this.unitFilterEntry.getText();
- }
-
- /**
- * @return index of selected unit
+ * @return index of selected unit in unit viewer
* @since 2019-01-15
* @since v0.1.0
*/
- public int getUnitListSelection() {
- return this.unitNameList.getSelectedIndex();
+ public String getUnitViewerSelection() {
+ return this.unitNameList.getSelectedValue();
}
/**
@@ -764,7 +662,7 @@ final class UnitConverterGUI {
final JButton convertButton = new JButton("Convert!");
convertExpressionPanel.add(convertButton);
- convertButton.addActionListener(e -> this.presenter.convert());
+ convertButton.addActionListener(e -> this.presenter.convertExpressions());
}
{ // output of conversion
@@ -808,24 +706,11 @@ final class UnitConverterGUI {
unitLookupPanel.setLayout(new GridLayout());
- { // panel for listing and searching
- final JPanel listPanel = new JPanel();
- unitLookupPanel.add(listPanel);
-
- listPanel.setLayout(new BorderLayout());
+ { // search panel
+ unitLookupPanel.add(this.unitNameList);
- { // unit search box
- listPanel.add(this.unitFilterEntry, BorderLayout.PAGE_START);
- this.unitFilterEntry.addCaretListener(e -> this.presenter.unitFilterUpdated());
- }
-
- { // a list of units
- listPanel.add(new JScrollPane(this.unitNameList), BorderLayout.CENTER);
- this.unitNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
- this.unitNameList.addListSelectionListener(e -> {
- this.presenter.unitNameSelected();
- });
- }
+ this.unitNameList.getSearchList()
+ .addListSelectionListener(e -> this.presenter.unitNameSelected());
}
{ // the text box for unit's toString
@@ -842,23 +727,10 @@ final class UnitConverterGUI {
prefixLookupPanel.setLayout(new GridLayout(1, 2));
{ // panel for listing and seaching
- final JPanel prefixListPanel = new JPanel();
- prefixLookupPanel.add(prefixListPanel);
-
- prefixListPanel.setLayout(new BorderLayout());
-
- { // prefix search box
- prefixListPanel.add(this.prefixFilterEntry, BorderLayout.PAGE_START);
- this.prefixFilterEntry.addCaretListener(e -> this.presenter.prefixFilterUpdated());
- }
+ prefixLookupPanel.add(this.prefixNameList);
- { // a list of prefixes
- prefixListPanel.add(new JScrollPane(this.prefixNameList), BorderLayout.CENTER);
- this.prefixNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
- this.prefixNameList.addListSelectionListener(e -> {
- this.presenter.prefixSelected();
- });
- }
+ this.prefixNameList.getSearchList()
+ .addListSelectionListener(e -> this.presenter.prefixSelected());
}
{ // the text box for prefix's toString
@@ -876,7 +748,7 @@ final class UnitConverterGUI {
* text to set
* @since 2019-04-13
*/
- public void setDimensionBasedOutputText(final String text) {
+ public void setDimensionConverterOutputText(final String text) {
this.dimensionBasedOutput.setText(text);
}
@@ -888,12 +760,12 @@ final class UnitConverterGUI {
* @since 2019-01-15
* @since v0.1.0
*/
- public void setOutputText(final String text) {
+ public void setExpressionConverterOutputText(final String text) {
this.output.setText(text);
}
/**
- * Sets the text of the prefix text box.
+ * Sets the text of the prefix text box in the prefix viewer.
*
* @param text
* text to set
@@ -905,14 +777,15 @@ final class UnitConverterGUI {
}
/**
- * Sets the text of the unit text box.
+ * Sets the text of the unit text box in the unit viewer.
*
- * @param t
+ * @param text
* text to set
- * @see javax.swing.text.JTextComponent#setText(java.lang.String)
+ * @since 2019-01-15
+ * @since v0.1.0
*/
- public void setUnitTextBoxText(final String t) {
- this.unitTextBox.setText(t);
+ public void setUnitTextBoxText(final String text) {
+ this.unitTextBox.setText(text);
}
/**
diff --git a/unitsfile.txt b/unitsfile.txt
index 553fd5e..bda9b81 100755
--- a/unitsfile.txt
+++ b/unitsfile.txt
@@ -138,7 +138,7 @@ radian m / m
rad radian
steradian m^2 / m^2
sr steradian
-degree 360 / tau * radian
+degree tau / 360 radian
deg degree
° degree
--
cgit v1.2.3
From 0c1ccf1c444e6d458f2be11d0cf6461fee15cd11 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 16:13:01 -0400
Subject: Updated README; it is now dual licensed under GNU FDL & CC BY-SA 4.0
---
README.org | 490 ++++++++++++++++++++-
.../converterGUI/UnitConverterGUI.java | 3 +-
2 files changed, 480 insertions(+), 13 deletions(-)
diff --git a/README.org b/README.org
index 175db07..5500d68 100644
--- a/README.org
+++ b/README.org
@@ -7,7 +7,13 @@
- Viewer and Prefix Viewer which allow you to search through all of the available units and prefixes and learn details about them.
- All of SI included in default text file
- Choose your precision
-* How to Convert
+* How to Convert (in "Convert Units")
+To convert units, simply:
+ 1. Select the kind of units to convert (length/mass/time, etc.).
+ 2. Select the units to convert from and to (you can use the text boxes above to search)
+ 3. Enter a value to convert
+ 4. Press "Convert"
+* How to Convert (in "Convert Expressions")
To convert units, simply enter the first unit in the From box and the second unit in the To box. Press Convert and a result will appear in the output box. Both boxes accept unit expressions, so you can input things like ‘1.7 m’, ‘85 km/h’ and ‘35 A * 14 s’ into either box.
*Warning*: The space between the number and the unit name is required, or else the whole expression will be interpreted as a unit name. Spaces are not required for operators.
@@ -15,12 +21,15 @@ To convert units, simply enter the first unit in the From box and the second uni
Use the slider at the bottom to choose the maximum precision of the result, in significant digits, not decimal places.
* Units Files and Expressions
As mentioned previously, all units are loaded from a units file. The format for lines consists of a name for the unit, some tabs (not spaces), and an expression. The following operations are supported in expressions:
+ - Addition using the plus sign (+), which only works on compatible units
+ - Subtraction using the minus sign (-), which only works on compatible units
- Multiplication using spaces ( ) or asterisks (*)
- Division using the forward slash (/)
- Exponentiation using the caret (^)
-Every argument can be either a linear unit, a base unit or a number, except exponents which must be integers. There is no way to use brackets to manipulate order of operations.
+ - Brackets to manipulate order of operations
+Every argument can be either a linear unit, a base unit or a number, except exponents which must be integers.
-Example (Explanation provided after # sign, this will *not* work in a real units file):
+Example:
inch 25.4 mm # Define ‘inch’ as equal to the product of 25.4 and ‘mm’
@@ -28,17 +37,18 @@ mph mile / hour # Define ‘mph’ as equal to the quotient of ‘mile’ and
litre 0.001 m^3 # Define ‘litre’ as equal to the product of 0.001 and the unit ‘m’ raised to the exponent 3
-Lines that start with the number sign (#) and blank lines will be ignored.
+Anything after a number sign (#) and blank lines will be ignored.
-Unit prefixes are defined differently. When a unit name ends with the dash (-) character, it is interpreted as a prefix. Prefix expressions are not as powerful as unit expressions, they can only be:
- - a number (1000)
- - an exponent (10^3)
- - the name of another prefix without the dash (kilo)
+Unit prefixes are defined differently. When a unit name ends with the dash (-) character, it is interpreted as a prefix. Prefix expressions are not as powerful as unit expressions, they can only do:
+ - Multiplication using spaces ( ) or asterisks (*)
+ - Division using the forward slash (/)
+ - Exponentiation using the caret (^)
+Every argument can be a number or a prefix name, except exponents which much be integers
* Unit Prefixes
In SI, you can have a unit prefix, which you attach to the start of a unit and it multiplies the unit by a certain value. For example, milli-metre = 0.001 * metre. This unit converter supports unit prefixes, and you can put any number of prefixes before a linear or base unit to transform it (this includes non-SI units).
You can use more than one prefix at once (yottayottametre = really really long distance), but you may not convert prefixes alone. If you want to do that, use ‘unit’ or ‘u’ (ku = 1000000 mu). You can also use u for converting from numbers (pi = 3.141593 u).
-*Warning*: While the standard symbol for ‘deca’ is ‘da’, this program uses ‘D’ instead since ‘da’ can be confused with ‘deciatto’. Also, you may use capital letters for the symbols of ‘hecto’ and ‘kilo’.
+The default unit file allows you to use D-, H- and K- in place of da-, h- and k- if you want.
*Warning*: The standard prefixes will never use 1024 instead of 1000, even when operating on bits and bytes. Use ‘Ki’, ‘Mi’, ‘Gi’, ‘Ti’ and so on instead.
* Nonlinear Units
@@ -49,7 +59,7 @@ FROM: unit1(value)
TO: unit2
Nonlinear units cannot:
- - multiply, divide or exponentiate
+ - add, subtract, multiply, divide or exponentiate
- use prefixes
- be defined by unit files
@@ -57,6 +67,462 @@ To define a nonlinear unit, make an anonymous inner type (or any other subclass)
* Unit and Prefix Viewers
The unit and prefix viewers can be used to see the available units (without prefixes) and prefixes. Upon opening them, you will see a list of units or prefixes on your left. Using the text box above, the list can be filtered. When a unit is clicked on, details about will be displayed on the right.
* Copyright and Licences
-The Unit Converter program is Copyright (C) 2018, 2019 Adrien Hopkins. It is released under the terms of the AGPL v3 licence.
+The Unit Converter program is Copyright (C) 2018, 2019 Adrien Hopkins. It is released under the terms of the Aferro GNU General Public License, version 3.0 or any later version published by the Free Software Foundation. A copy of this license should be provided with this program, and a human-readable summary of the very similar GNU General Public License can be found at the following link: https://www.gnu.org/licenses/quick-guide-gplv3.html, although this summary is NOT a replacement for the actual license.
+
+This document is Copyright (C) 2019 Adrien Hopkins. This document is dual-licensed under the terms of the GNU Free Documentation License and the Creative Commons Attribution-ShareAlike License. More details are in the next paragraphs:
+
+Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".
+
+This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
+** GNU Free Documentation License
+
+ GNU Free Documentation License
+ Version 1.3, 3 November 2008
+
+
+ Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+0. PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document "free" in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of "copyleft", which means that derivative
+works of the document must themselves be free in the same sense. It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does. But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book. We recommend this License
+principally for works whose purpose is instruction or reference.
+
+
+1. APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License. Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein. The "Document", below,
+refers to any such manual or work. Any member of the public is a
+licensee, and is addressed as "you". You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A "Modified Version" of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A "Secondary Section" is a named appendix or a front-matter section of
+the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall
+subject (or to related matters) and contains nothing that could fall
+directly within that overall subject. (Thus, if the Document is in
+part a textbook of mathematics, a Secondary Section may not explain
+any mathematics.) The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The "Invariant Sections" are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License. If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant. The Document may contain zero
+Invariant Sections. If the Document does not identify any Invariant
+Sections then there are none.
+
+The "Cover Texts" are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License. A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A "Transparent" copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters. A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text. A copy that is not "Transparent" is called "Opaque".
+
+Examples of suitable formats for Transparent copies include plain
+ASCII without markup, Texinfo input format, LaTeX input format, SGML
+or XML using a publicly available DTD, and standard-conforming simple
+HTML, PostScript or PDF designed for human modification. Examples of
+transparent image formats include PNG, XCF and JPG. Opaque formats
+include proprietary formats that can be read and edited only by
+proprietary word processors, SGML or XML for which the DTD and/or
+processing tools are not generally available, and the
+machine-generated HTML, PostScript or PDF produced by some word
+processors for output purposes only.
+
+The "Title Page" means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page. For works in
+formats which do not have any title page as such, "Title Page" means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+The "publisher" means any person or entity that distributes copies of
+the Document to the public.
+
+A section "Entitled XYZ" means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language. (Here XYZ stands for a
+specific section name mentioned below, such as "Acknowledgements",
+"Dedications", "Endorsements", or "History".) To "Preserve the Title"
+of such a section when you modify the Document means that it remains a
+section "Entitled XYZ" according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document. These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+2. VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no
+other conditions whatsoever to those of this License. You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute. However, you may accept
+compensation in exchange for copies. If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+
+3. COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover. Both covers must also clearly and legibly identify
+you as the publisher of these copies. The front cover must present
+the full title with all words of the title equally prominent and
+visible. You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to
+give them a chance to provide you with an updated version of the
+Document.
+
+
+4. MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it. In addition, you must do these things in the Modified Version:
+
+A. Use in the Title Page (and on the covers, if any) a title distinct
+ from that of the Document, and from those of previous versions
+ (which should, if there were any, be listed in the History section
+ of the Document). You may use the same title as a previous version
+ if the original publisher of that version gives permission.
+B. List on the Title Page, as authors, one or more persons or entities
+ responsible for authorship of the modifications in the Modified
+ Version, together with at least five of the principal authors of the
+ Document (all of its principal authors, if it has fewer than five),
+ unless they release you from this requirement.
+C. State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.
+D. Preserve all the copyright notices of the Document.
+E. Add an appropriate copyright notice for your modifications
+ adjacent to the other copyright notices.
+F. Include, immediately after the copyright notices, a license notice
+ giving the public permission to use the Modified Version under the
+ terms of this License, in the form shown in the Addendum below.
+G. Preserve in that license notice the full lists of Invariant Sections
+ and required Cover Texts given in the Document's license notice.
+H. Include an unaltered copy of this License.
+I. Preserve the section Entitled "History", Preserve its Title, and add
+ to it an item stating at least the title, year, new authors, and
+ publisher of the Modified Version as given on the Title Page. If
+ there is no section Entitled "History" in the Document, create one
+ stating the title, year, authors, and publisher of the Document as
+ given on its Title Page, then add an item describing the Modified
+ Version as stated in the previous sentence.
+J. Preserve the network location, if any, given in the Document for
+ public access to a Transparent copy of the Document, and likewise
+ the network locations given in the Document for previous versions
+ it was based on. These may be placed in the "History" section.
+ You may omit a network location for a work that was published at
+ least four years before the Document itself, or if the original
+ publisher of the version it refers to gives permission.
+K. For any section Entitled "Acknowledgements" or "Dedications",
+ Preserve the Title of the section, and preserve in the section all
+ the substance and tone of each of the contributor acknowledgements
+ and/or dedications given therein.
+L. Preserve all the Invariant Sections of the Document,
+ unaltered in their text and in their titles. Section numbers
+ or the equivalent are not considered part of the section titles.
+M. Delete any section Entitled "Endorsements". Such a section
+ may not be included in the Modified Version.
+N. Do not retitle any existing section to be Entitled "Endorsements"
+ or to conflict in title with any Invariant Section.
+O. Preserve any Warranty Disclaimers.
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant. To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled "Endorsements", provided it contains
+nothing but endorsements of your Modified Version by various
+parties--for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version. Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity. If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+
+5. COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy. If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled "History"
+in the various original documents, forming one section Entitled
+"History"; likewise combine any sections Entitled "Acknowledgements",
+and any sections Entitled "Dedications". You must delete all sections
+Entitled "Endorsements".
+
+
+6. COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other
+documents released under this License, and replace the individual
+copies of this License in the various documents with a single copy
+that is included in the collection, provided that you follow the rules
+of this License for verbatim copying of each of the documents in all
+other respects.
+
+You may extract a single document from such a collection, and
+distribute it individually under this License, provided you insert a
+copy of this License into the extracted document, and follow this
+License in all other respects regarding verbatim copying of that
+document.
+
+
+7. AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an "aggregate" if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included in an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+
+8. TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections. You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers. In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled "Acknowledgements",
+"Dedications", or "History", the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+
+9. TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense, or distribute it is void, and
+will automatically terminate your rights under this License.
+
+However, if you cease all violation of this License, then your license
+from a particular copyright holder is reinstated (a) provisionally,
+unless and until the copyright holder explicitly and finally
+terminates your license, and (b) permanently, if the copyright holder
+fails to notify you of the violation by some reasonable means prior to
+60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, receipt of a copy of some or all of the same material does
+not give you any rights to use it.
+
+
+10. FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions of the
+GNU Free Documentation License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in
+detail to address new problems or concerns. See
+https://www.gnu.org/licenses/.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation. If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation. If the Document
+specifies that a proxy can decide which future versions of this
+License can be used, that proxy's public statement of acceptance of a
+version permanently authorizes you to choose that version for the
+Document.
+
+11. RELICENSING
+
+"Massive Multiauthor Collaboration Site" (or "MMC Site") means any
+World Wide Web server that publishes copyrightable works and also
+provides prominent facilities for anybody to edit those works. A
+public wiki that anybody can edit is an example of such a server. A
+"Massive Multiauthor Collaboration" (or "MMC") contained in the site
+means any set of copyrightable works thus published on the MMC site.
+
+"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0
+license published by Creative Commons Corporation, a not-for-profit
+corporation with a principal place of business in San Francisco,
+California, as well as future copyleft versions of that license
+published by that same organization.
+
+"Incorporate" means to publish or republish a Document, in whole or in
+part, as part of another Document.
+
+An MMC is "eligible for relicensing" if it is licensed under this
+License, and if all works that were first published under this License
+somewhere other than this MMC, and subsequently incorporated in whole or
+in part into the MMC, (1) had no cover texts or invariant sections, and
+(2) were thus incorporated prior to November 1, 2008.
+
+The operator of an MMC Site may republish an MMC contained in the site
+under CC-BY-SA on the same site at any time before August 1, 2009,
+provided the MMC is eligible for relicensing.
+
+
+ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+ Copyright (c) YEAR YOUR NAME.
+ Permission is granted to copy, distribute and/or modify this document
+ under the terms of the GNU Free Documentation License, Version 1.3
+ or any later version published by the Free Software Foundation;
+ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+ A copy of the license is included in the section entitled "GNU
+ Free Documentation License".
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the "with...Texts." line with this:
+
+ with the Invariant Sections being LIST THEIR TITLES, with the
+ Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
-This document is Copyright (C) 2019 Adrien Hopkins. It is released under the terms of the CC BY-SA (Creative Commons Attribution-ShareAlike) licence.
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index cacc3b7..aecb64f 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -735,7 +735,8 @@ final class UnitConverterGUI {
{ // the text box for prefix's toString
prefixLookupPanel.add(this.prefixTextBox);
- this.unitTextBox.setEditable(false);
+ this.prefixTextBox.setEditable(false);
+ this.prefixTextBox.setLineWrap(true);
}
}
}
--
cgit v1.2.3
From 181f32d80a9fb7887e93666b2f1ef10d62622789 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 16:18:20 -0400
Subject: Bugfix: Entering an empty string into To no longer crashes.
---
src/org/unitConverter/converterGUI/UnitConverterGUI.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index aecb64f..1f59e3a 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -216,6 +216,10 @@ final class UnitConverterGUI {
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.");
+ return;
+ }
// try to parse from
final Unit from;
--
cgit v1.2.3
From d7a587694d857fa468c7aae6a5f4fda24f3577fa Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 16:35:05 -0400
Subject: Final tweaks to the API for now.
---
src/org/unitConverter/converterGUI/FilterComparator.java | 4 ++--
src/test/java/ExpressionParserTest.java | 2 ++
src/test/java/UnitDimensionTest.java | 2 +-
src/test/java/UnitTest.java | 2 +-
src/test/java/UnitsDatabaseTest.java | 2 +-
src/test/java/package-info.java | 2 +-
6 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/org/unitConverter/converterGUI/FilterComparator.java b/src/org/unitConverter/converterGUI/FilterComparator.java
index ef94602..2d0e7f9 100755
--- a/src/org/unitConverter/converterGUI/FilterComparator.java
+++ b/src/org/unitConverter/converterGUI/FilterComparator.java
@@ -20,13 +20,13 @@ import java.util.Comparator;
import java.util.Objects;
/**
- * A comparator that compares strings using a filter. It is case-insensitive
+ * A comparator that compares strings using a filter.
*
* @author Adrien Hopkins
* @since 2019-01-15
* @since v0.1.0
*/
-public final class FilterComparator implements Comparator {
+final class FilterComparator implements Comparator {
/**
* The filter that the comparator is filtered by.
*
diff --git a/src/test/java/ExpressionParserTest.java b/src/test/java/ExpressionParserTest.java
index e81ca40..62fa964 100644
--- a/src/test/java/ExpressionParserTest.java
+++ b/src/test/java/ExpressionParserTest.java
@@ -22,6 +22,8 @@ import org.junit.Test;
import org.unitConverter.math.ExpressionParser;
/**
+ * A test for the {@code ExpressionParser} class. This is NOT part of this program's public API.
+ *
* @author Adrien Hopkins
* @since 2019-03-22
*/
diff --git a/src/test/java/UnitDimensionTest.java b/src/test/java/UnitDimensionTest.java
index 0b5055b..587cf4c 100755
--- a/src/test/java/UnitDimensionTest.java
+++ b/src/test/java/UnitDimensionTest.java
@@ -32,7 +32,7 @@ import org.unitConverter.dimension.SIBaseDimension;
import org.unitConverter.dimension.UnitDimension;
/**
- * Tests for {@link UnitDimension}.
+ * Tests for {@link UnitDimension}. This is NOT part of this program's public API.
*
* @author Adrien Hopkins
* @since 2018-12-12
diff --git a/src/test/java/UnitTest.java b/src/test/java/UnitTest.java
index 79bc3d1..952b6f2 100755
--- a/src/test/java/UnitTest.java
+++ b/src/test/java/UnitTest.java
@@ -31,7 +31,7 @@ import org.unitConverter.unit.SIPrefix;
import org.unitConverter.unit.Unit;
/**
- * Testing the various Unit classes
+ * Testing the various Unit classes. This is NOT part of this program's public API.
*
* @author Adrien Hopkins
* @since 2018-12-22
diff --git a/src/test/java/UnitsDatabaseTest.java b/src/test/java/UnitsDatabaseTest.java
index 39f95a5..8429561 100644
--- a/src/test/java/UnitsDatabaseTest.java
+++ b/src/test/java/UnitsDatabaseTest.java
@@ -34,7 +34,7 @@ import org.unitConverter.unit.Unit;
import org.unitConverter.unit.UnitPrefix;
/**
- * A test for the {@link UnitsDatabase} class.
+ * A test for the {@link UnitsDatabase} class. This is NOT part of this program's public API.
*
* @author Adrien Hopkins
* @since 2019-04-14
diff --git a/src/test/java/package-info.java b/src/test/java/package-info.java
index 9f2e1d6..87b4a06 100644
--- a/src/test/java/package-info.java
+++ b/src/test/java/package-info.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
/**
- * All of the Unit Converter tests.
+ * All of the Unit Converter tests. Everything in this package is NOT part of Unit Converter's public API.
*
* @author Adrien Hopkins
* @since 2019-03-16
--
cgit v1.2.3
From 73d305684d3549d17ebd95a5fdb7d366849db226 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 17:29:50 -0400
Subject: Added @since tags to all classes and methods from v0.2.0
---
src/org/unitConverter/UnitsDatabase.java | 47 +++++++++++++++++++++-
.../converterGUI/FilterComparator.java | 2 +
.../converterGUI/MutablePredicate.java | 10 +++++
.../unitConverter/converterGUI/SearchBoxList.java | 35 ++++++++++++++++
.../converterGUI/UnitConverterGUI.java | 13 ++++++
.../unitConverter/converterGUI/package-info.java | 1 +
src/org/unitConverter/dimension/package-info.java | 1 +
src/org/unitConverter/math/DecimalComparison.java | 7 ++++
src/org/unitConverter/math/ExpressionParser.java | 46 ++++++++++++++++++++-
src/org/unitConverter/unit/AbstractUnit.java | 1 -
src/org/unitConverter/unit/BaseUnit.java | 1 +
src/org/unitConverter/unit/DefaultUnitPrefix.java | 1 +
src/org/unitConverter/unit/LinearUnit.java | 3 ++
src/org/unitConverter/unit/UnitPrefix.java | 3 ++
src/org/unitConverter/unit/package-info.java | 1 +
src/test/java/ExpressionParserTest.java | 1 +
src/test/java/UnitTest.java | 1 +
src/test/java/UnitsDatabaseTest.java | 7 ++++
src/test/java/package-info.java | 1 +
19 files changed, 179 insertions(+), 3 deletions(-)
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index abe6546..e5d2f67 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -72,9 +72,15 @@ public final class UnitsDatabase {
* "A", inputting "ABC" will return the unit "C" with the prefix "AB", not "BC" with the prefix "A".
*
*
+ *
+ * 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 UnsupportedOperationException}.
+ *
*
* @author Adrien Hopkins
* @since 2019-04-13
+ * @since v0.2.0
*/
private static final class PrefixedUnitMap implements Map {
/**
@@ -82,6 +88,7 @@ public final class UnitsDatabase {
*
* @author Adrien Hopkins
* @since 2019-04-13
+ * @since v0.2.0
*/
private static final class PrefixedUnitEntrySet extends AbstractSet> {
/**
@@ -89,6 +96,7 @@ public final class UnitsDatabase {
*
* @author Adrien Hopkins
* @since 2019-04-14
+ * @since v0.2.0
*/
private static final class PrefixedUnitEntry implements Entry {
private final String key;
@@ -98,8 +106,11 @@ public final class UnitsDatabase {
* Creates the {@code PrefixedUnitEntry}.
*
* @param key
+ * key
* @param value
+ * value
* @since 2019-04-14
+ * @since v0.2.0
*/
public PrefixedUnitEntry(final String key, final Unit value) {
this.key = key;
@@ -127,6 +138,7 @@ public final class UnitsDatabase {
*
* @author Adrien Hopkins
* @since 2019-04-14
+ * @since v0.2.0
*/
private static final class PrefixedUnitEntryIterator implements Iterator> {
// position in the unit list
@@ -143,6 +155,7 @@ public final class UnitsDatabase {
* Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
public PrefixedUnitEntryIterator(final PrefixedUnitEntrySet set) {
this.map = set.map;
@@ -153,6 +166,7 @@ public final class UnitsDatabase {
/**
* @return current unit name
* @since 2019-04-14
+ * @since v0.2.0
*/
private String getCurrentUnitName() {
final StringBuilder unitName = new StringBuilder();
@@ -180,6 +194,7 @@ public final class UnitsDatabase {
* Changes this iterator's position to the next available one.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
private void incrementPosition() {
this.unitNamePosition++;
@@ -239,7 +254,9 @@ public final class UnitsDatabase {
* Creates the {@code PrefixedUnitNameSet}.
*
* @param map
+ * map that created this set
* @since 2019-04-13
+ * @since v0.2.0
*/
public PrefixedUnitEntrySet(final PrefixedUnitMap map) {
this.map = map;
@@ -353,6 +370,7 @@ public final class UnitsDatabase {
*
* @author Adrien Hopkins
* @since 2019-04-13
+ * @since v0.2.0
*/
private static final class PrefixedUnitNameSet extends AbstractSet {
/**
@@ -360,6 +378,7 @@ public final class UnitsDatabase {
*
* @author Adrien Hopkins
* @since 2019-04-14
+ * @since v0.2.0
*/
private static final class PrefixedUnitNameIterator implements Iterator {
// position in the unit list
@@ -376,6 +395,7 @@ public final class UnitsDatabase {
* Creates the {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
public PrefixedUnitNameIterator(final PrefixedUnitNameSet set) {
this.map = set.map;
@@ -386,6 +406,7 @@ public final class UnitsDatabase {
/**
* @return current unit name
* @since 2019-04-14
+ * @since v0.2.0
*/
private String getCurrentUnitName() {
final StringBuilder unitName = new StringBuilder();
@@ -413,6 +434,7 @@ public final class UnitsDatabase {
* Changes this iterator's position to the next available one.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
private void incrementPosition() {
this.unitNamePosition++;
@@ -472,7 +494,9 @@ public final class UnitsDatabase {
* Creates the {@code PrefixedUnitNameSet}.
*
* @param map
+ * map that created this set
* @since 2019-04-13
+ * @since v0.2.0
*/
public PrefixedUnitNameSet(final PrefixedUnitMap map) {
this.map = map;
@@ -567,13 +591,13 @@ public final class UnitsDatabase {
// infinite set
throw new UnsupportedOperationException("Cannot make an infinite set into an array.");
}
-
}
/**
* The units stored in this collection, without prefixes.
*
* @since 2019-04-13
+ * @since v0.2.0
*/
private final Map units;
@@ -581,6 +605,7 @@ public final class UnitsDatabase {
* The available prefixes for use.
*
* @since 2019-04-13
+ * @since v0.2.0
*/
private final Map prefixes;
@@ -593,8 +618,11 @@ public final class UnitsDatabase {
* Creates the {@code PrefixedUnitMap}.
*
* @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 units, final Map prefixes) {
// I am making unmodifiable maps to ensure I don't accidentally make changes.
@@ -804,6 +832,7 @@ public final class UnitsDatabase {
* exponent
* @return result
* @since 2019-04-10
+ * @since v0.2.0
*/
private static final LinearUnit exponentiateUnits(final LinearUnit base, final LinearUnit exponentUnit) {
// exponent function - first check if o2 is a number,
@@ -841,6 +870,7 @@ public final class UnitsDatabase {
* The dimensions in this system.
*
* @since 2019-03-14
+ * @since v0.2.0
*/
private final Map dimensions;
@@ -848,6 +878,7 @@ public final class UnitsDatabase {
* A map mapping strings to units (including prefixes)
*
* @since 2019-04-13
+ * @since v0.2.0
*/
private final Map units;
@@ -855,6 +886,7 @@ public final class UnitsDatabase {
* A parser that can parse unit expressions.
*
* @since 2019-03-22
+ * @since v0.2.0
*/
private final ExpressionParser unitExpressionParser = new ExpressionParser.Builder<>(
this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
@@ -867,6 +899,7 @@ public final class UnitsDatabase {
* A parser that can parse unit prefix expressions
*
* @since 2019-04-13
+ * @since v0.2.0
*/
private final ExpressionParser prefixExpressionParser = new ExpressionParser.Builder<>(this::getPrefix)
.addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0).addSpaceFunction("*")
@@ -877,6 +910,7 @@ public final class UnitsDatabase {
* A parser that can parse unit dimension expressions.
*
* @since 2019-04-13
+ * @since v0.2.0
*/
private final ExpressionParser unitDimensionParser = new ExpressionParser.Builder<>(
this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0).addSpaceFunction("*")
@@ -905,6 +939,7 @@ public final class UnitsDatabase {
* @throws NullPointerException
* if name or dimension is null
* @since 2019-03-14
+ * @since v0.2.0
*/
public void addDimension(final String name, final UnitDimension dimension) {
this.dimensions.put(Objects.requireNonNull(name, "name must not be null."),
@@ -919,6 +954,7 @@ public final class UnitsDatabase {
* @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) {
// ignore lines that start with a # sign - they're comments
@@ -1004,6 +1040,7 @@ public final class UnitsDatabase {
* @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) {
// ignore lines that start with a # sign - they're comments
@@ -1064,6 +1101,7 @@ public final class UnitsDatabase {
* name to test
* @return if database contains name
* @since 2019-03-14
+ * @since v0.2.0
*/
public boolean containsDimensionName(final String name) {
return this.dimensions.containsKey(name);
@@ -1098,6 +1136,7 @@ public final class UnitsDatabase {
/**
* @return a map mapping dimension names to dimensions
* @since 2019-04-13
+ * @since v0.2.0
*/
public Map dimensionMap() {
return Collections.unmodifiableMap(this.dimensions);
@@ -1114,6 +1153,7 @@ public final class UnitsDatabase {
* dimension's name
* @return dimension
* @since 2019-03-14
+ * @since v0.2.0
*/
public UnitDimension getDimension(final String name) {
Objects.requireNonNull(name, "name must not be null.");
@@ -1152,6 +1192,7 @@ public final class UnitsDatabase {
* @throws NullPointerException
* if expression is null
* @since 2019-04-13
+ * @since v0.2.0
*/
public UnitDimension getDimensionFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
@@ -1180,6 +1221,7 @@ public final class UnitsDatabase {
* 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)
@@ -1411,6 +1453,7 @@ public final class UnitsDatabase {
/**
* @return a map mapping prefix names to prefixes
* @since 2019-04-13
+ * @since v0.2.0
*/
public Map prefixMap() {
return Collections.unmodifiableMap(this.prefixes);
@@ -1419,6 +1462,7 @@ public final class UnitsDatabase {
/**
* @return a map mapping unit names to units, including prefixed names
* @since 2019-04-13
+ * @since v0.2.0
*/
public Map unitMap() {
return this.units; // PrefixedUnitMap is immutable so I don't need to make an unmodifiable map.
@@ -1427,6 +1471,7 @@ public final class UnitsDatabase {
/**
* @return a map mapping unit names to units, ignoring prefixes
* @since 2019-04-13
+ * @since v0.2.0
*/
public Map unitMapPrefixless() {
return Collections.unmodifiableMap(this.prefixlessUnits);
diff --git a/src/org/unitConverter/converterGUI/FilterComparator.java b/src/org/unitConverter/converterGUI/FilterComparator.java
index 2d0e7f9..7b17bfc 100755
--- a/src/org/unitConverter/converterGUI/FilterComparator.java
+++ b/src/org/unitConverter/converterGUI/FilterComparator.java
@@ -45,6 +45,7 @@ final class FilterComparator implements Comparator {
* Whether or not the comparison is case-sensitive.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
private final boolean caseSensitive;
@@ -87,6 +88,7 @@ final class FilterComparator implements Comparator {
* @throws NullPointerException
* if filter is null
* @since 2019-04-14
+ * @since v0.2.0
*/
public FilterComparator(final String filter, final Comparator comparator, final boolean caseSensitive) {
this.filter = Objects.requireNonNull(filter, "filter must not be null.");
diff --git a/src/org/unitConverter/converterGUI/MutablePredicate.java b/src/org/unitConverter/converterGUI/MutablePredicate.java
index 157903c..e15b3cd 100644
--- a/src/org/unitConverter/converterGUI/MutablePredicate.java
+++ b/src/org/unitConverter/converterGUI/MutablePredicate.java
@@ -23,14 +23,22 @@ import java.util.function.Predicate;
*
* @author Adrien Hopkins
* @since 2019-04-13
+ * @since v0.2.0
*/
final class MutablePredicate implements Predicate {
+ /**
+ * The predicate stored in this {@code MutablePredicate}
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
+ */
private Predicate predicate;
/**
* Creates the {@code MutablePredicate}.
*
* @since 2019-04-13
+ * @since v0.2.0
*/
public MutablePredicate(final Predicate predicate) {
this.predicate = predicate;
@@ -39,6 +47,7 @@ final class MutablePredicate implements Predicate {
/**
* @return predicate
* @since 2019-04-13
+ * @since v0.2.0
*/
public final Predicate getPredicate() {
return this.predicate;
@@ -48,6 +57,7 @@ final class MutablePredicate implements Predicate {
* @param predicate
* new value of predicate
* @since 2019-04-13
+ * @since v0.2.0
*/
public final void setPredicate(final Predicate predicate) {
this.predicate = predicate;
diff --git a/src/org/unitConverter/converterGUI/SearchBoxList.java b/src/org/unitConverter/converterGUI/SearchBoxList.java
index 35cc347..1995466 100644
--- a/src/org/unitConverter/converterGUI/SearchBoxList.java
+++ b/src/org/unitConverter/converterGUI/SearchBoxList.java
@@ -33,20 +33,29 @@ import javax.swing.JTextField;
/**
* @author Adrien Hopkins
* @since 2019-04-13
+ * @since v0.2.0
*/
final class SearchBoxList extends JPanel {
/**
* @since 2019-04-13
+ * @since v0.2.0
*/
private static final long serialVersionUID = 6226930279415983433L;
/**
* The text to place in an empty search box.
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
*/
private static final String EMPTY_TEXT = "Search...";
+
/**
* The color to use for an empty foreground.
+ *
+ * @since 2019-04-13
+ * @since v0.2.0
*/
private static final Color EMPTY_FOREGROUND = new Color(192, 192, 192);
@@ -66,6 +75,13 @@ final class SearchBoxList extends JPanel {
private final Comparator defaultOrdering;
private final boolean caseSensitive;
+ /**
+ * Creates the {@code SearchBoxList}.
+ *
+ * @param itemsToFilter
+ * items to put in the list
+ * @since 2019-04-14
+ */
public SearchBoxList(final Collection itemsToFilter) {
this(itemsToFilter, null, false);
}
@@ -73,7 +89,15 @@ final class SearchBoxList extends JPanel {
/**
* Creates the {@code SearchBoxList}.
*
+ * @param itemsToFilter
+ * items to put in the list
+ * @param defaultOrdering
+ * default ordering of items after filtration (null=Comparable)
+ * @param caseSensitive
+ * whether or not the filtration is case-sensitive
+ *
* @since 2019-04-13
+ * @since v0.2.0
*/
public SearchBoxList(final Collection itemsToFilter, final Comparator defaultOrdering,
final boolean caseSensitive) {
@@ -116,6 +140,7 @@ final class SearchBoxList extends JPanel {
* @param filter
* filter to add.
* @since 2019-04-13
+ * @since v0.2.0
*/
public void addSearchFilter(final Predicate filter) {
this.customSearchFilter = this.customSearchFilter.and(filter);
@@ -125,6 +150,7 @@ final class SearchBoxList extends JPanel {
* Resets the search filter.
*
* @since 2019-04-13
+ * @since v0.2.0
*/
public void clearSearchFilters() {
this.customSearchFilter = o -> true;
@@ -133,6 +159,7 @@ final class SearchBoxList extends JPanel {
/**
* @return this component's search box component
* @since 2019-04-14
+ * @since v0.2.0
*/
public final JTextField getSearchBox() {
return this.searchBox;
@@ -143,6 +170,7 @@ final class SearchBoxList extends JPanel {
* text to search for
* @return a filter that filters out that text, based on this list's case sensitive setting
* @since 2019-04-14
+ * @since v0.2.0
*/
private Predicate getSearchFilter(final String searchText) {
if (this.caseSensitive)
@@ -154,6 +182,7 @@ final class SearchBoxList extends JPanel {
/**
* @return this component's list component
* @since 2019-04-14
+ * @since v0.2.0
*/
public final JList getSearchList() {
return this.searchItems;
@@ -162,6 +191,7 @@ final class SearchBoxList extends JPanel {
/**
* @return index selected in item list
* @since 2019-04-14
+ * @since v0.2.0
*/
public int getSelectedIndex() {
return this.searchItems.getSelectedIndex();
@@ -170,6 +200,7 @@ final class SearchBoxList extends JPanel {
/**
* @return value selected in item list
* @since 2019-04-13
+ * @since v0.2.0
*/
public String getSelectedValue() {
return this.searchItems.getSelectedValue();
@@ -179,6 +210,7 @@ final class SearchBoxList extends JPanel {
* Re-applies the filters.
*
* @since 2019-04-13
+ * @since v0.2.0
*/
public void reapplyFilter() {
final String searchText = this.searchBoxEmpty ? "" : this.searchBox.getText();
@@ -205,6 +237,7 @@ final class SearchBoxList extends JPanel {
* @param e
* focus event
* @since 2019-04-13
+ * @since v0.2.0
*/
private void searchBoxFocusGained(final FocusEvent e) {
this.searchBoxFocused = true;
@@ -220,6 +253,7 @@ final class SearchBoxList extends JPanel {
* @param e
* focus event
* @since 2019-04-13
+ * @since v0.2.0
*/
private void searchBoxFocusLost(final FocusEvent e) {
this.searchBoxFocused = false;
@@ -236,6 +270,7 @@ final class SearchBoxList extends JPanel {
*
*
* @since 2019-04-14
+ * @since v0.2.0
*/
private void searchBoxTextChanged() {
if (this.searchBoxFocused) {
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 1f59e3a..e258c6f 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -64,6 +64,7 @@ final class UnitConverterGUI {
* @param database
* database to add to
* @since 2019-04-14
+ * @since v0.2.0
*/
private static void addDefaults(final UnitsDatabase database) {
database.addUnit("metre", SI.METRE);
@@ -167,6 +168,7 @@ final class UnitConverterGUI {
* Converts in the dimension-based converter
*
* @since 2019-04-13
+ * @since v0.2.0
*/
public final void convertDimensionBased() {
final String fromSelection = this.view.getFromSelection();
@@ -264,6 +266,7 @@ final class UnitConverterGUI {
/**
* @return a list of all of the unit dimensions
* @since 2019-04-13
+ * @since v0.2.0
*/
public final List dimensionNameList() {
return this.dimensionNames;
@@ -272,6 +275,7 @@ final class UnitConverterGUI {
/**
* @return a comparator to compare prefix names
* @since 2019-04-14
+ * @since v0.2.0
*/
public final Comparator getPrefixNameComparator() {
return this.prefixNameComparator;
@@ -282,6 +286,7 @@ final class UnitConverterGUI {
* value to round
* @return string of that value rounded to {@code significantDigits} significant digits.
* @since 2019-04-14
+ * @since v0.2.0
*/
private final String getRoundedString(final double value) {
// round value
@@ -304,6 +309,7 @@ final class UnitConverterGUI {
/**
* @return a set of all prefix names in the database
* @since 2019-04-14
+ * @since v0.2.0
*/
public final Set prefixNameSet() {
return this.database.prefixMap().keySet();
@@ -333,6 +339,7 @@ final class UnitConverterGUI {
* @param significantFigures
* new value of significantFigures
* @since 2019-01-15
+ * @since v0.1.0
*/
public final void setSignificantFigures(final int significantFigures) {
this.significantFigures = significantFigures;
@@ -348,6 +355,7 @@ final class UnitConverterGUI {
* name of dimension to test
* @return whether unit has dimenision
* @since 2019-04-13
+ * @since v0.2.0
*/
public final boolean unitMatchesDimension(final String unitName, final String dimensionName) {
final Unit unit = this.database.getUnit(unitName);
@@ -378,6 +386,7 @@ final class UnitConverterGUI {
/**
* @return a set of all of the unit names
* @since 2019-04-14
+ * @since v0.2.0
*/
public final Set unitNameSet() {
return this.database.unitMapPrefixless().keySet();
@@ -452,6 +461,7 @@ final class UnitConverterGUI {
/**
* @return value in dimension-based converter
* @since 2019-04-13
+ * @since v0.2.0
*/
public String getDimensionConverterInput() {
return this.valueInput.getText();
@@ -460,6 +470,7 @@ final class UnitConverterGUI {
/**
* @return selection in "From" selector in dimension-based converter
* @since 2019-04-13
+ * @since v0.2.0
*/
public String getFromSelection() {
return this.fromSearch.getSelectedValue();
@@ -486,6 +497,7 @@ final class UnitConverterGUI {
/**
* @return selection in "To" selector in dimension-based converter
* @since 2019-04-13
+ * @since v0.2.0
*/
public String getToSelection() {
return this.toSearch.getSelectedValue();
@@ -752,6 +764,7 @@ final class UnitConverterGUI {
* @param text
* text to set
* @since 2019-04-13
+ * @since v0.2.0
*/
public void setDimensionConverterOutputText(final String text) {
this.dimensionBasedOutput.setText(text);
diff --git a/src/org/unitConverter/converterGUI/package-info.java b/src/org/unitConverter/converterGUI/package-info.java
index d899f97..1555291 100644
--- a/src/org/unitConverter/converterGUI/package-info.java
+++ b/src/org/unitConverter/converterGUI/package-info.java
@@ -19,5 +19,6 @@
*
* @author Adrien Hopkins
* @since 2019-01-25
+ * @since v0.2.0
*/
package org.unitConverter.converterGUI;
\ No newline at end of file
diff --git a/src/org/unitConverter/dimension/package-info.java b/src/org/unitConverter/dimension/package-info.java
index db363df..8cb26b1 100755
--- a/src/org/unitConverter/dimension/package-info.java
+++ b/src/org/unitConverter/dimension/package-info.java
@@ -19,5 +19,6 @@
*
* @author Adrien Hopkins
* @since 2018-12-22
+ * @since v0.1.0
*/
package org.unitConverter.dimension;
\ No newline at end of file
diff --git a/src/org/unitConverter/math/DecimalComparison.java b/src/org/unitConverter/math/DecimalComparison.java
index e6fb733..7cdbe5b 100644
--- a/src/org/unitConverter/math/DecimalComparison.java
+++ b/src/org/unitConverter/math/DecimalComparison.java
@@ -21,6 +21,7 @@ package org.unitConverter.math;
*
* @author Adrien Hopkins
* @since 2019-03-18
+ * @since v0.2.0
*/
public final class DecimalComparison {
/**
@@ -28,6 +29,7 @@ public final class DecimalComparison {
* they are considered equal.
*
* @since 2019-03-18
+ * @since v0.2.0
*/
public static final double DOUBLE_EPSILON = 1.0e-15;
@@ -36,6 +38,7 @@ public final class DecimalComparison {
* they are considered equal.
*
* @since 2019-03-18
+ * @since v0.2.0
*/
public static final float FLOAT_EPSILON = 1.0e-6f;
@@ -48,6 +51,7 @@ public final class DecimalComparison {
* second value to test
* @return whether they are equal
* @since 2019-03-18
+ * @since v0.2.0
*/
public static final boolean equals(final double a, final double b) {
return DecimalComparison.equals(a, b, DOUBLE_EPSILON);
@@ -64,6 +68,7 @@ public final class DecimalComparison {
* allowed difference
* @return whether they are equal
* @since 2019-03-18
+ * @since v0.2.0
*/
public static final boolean equals(final double a, final double b, final double epsilon) {
return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
@@ -78,6 +83,7 @@ public final class DecimalComparison {
* second value to test
* @return whether they are equal
* @since 2019-03-18
+ * @since v0.2.0
*/
public static final boolean equals(final float a, final float b) {
return DecimalComparison.equals(a, b, FLOAT_EPSILON);
@@ -94,6 +100,7 @@ public final class DecimalComparison {
* allowed difference
* @return whether they are equal
* @since 2019-03-18
+ * @since v0.2.0
*/
public static final boolean equals(final float a, final float b, final float epsilon) {
return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
diff --git a/src/org/unitConverter/math/ExpressionParser.java b/src/org/unitConverter/math/ExpressionParser.java
index d01afaa..b2261ed 100644
--- a/src/org/unitConverter/math/ExpressionParser.java
+++ b/src/org/unitConverter/math/ExpressionParser.java
@@ -35,8 +35,8 @@ import java.util.function.UnaryOperator;
* @param
* type of object that exists in parsed expressions
* @since 2019-03-14
+ * @since v0.2.0
*/
-// TODO: possibly make this class non-final?
public final class ExpressionParser {
/**
* A builder that can create {@code ExpressionParser} instances.
@@ -45,6 +45,7 @@ public final class ExpressionParser {
* @param
* type of object that exists in parsed expressions
* @since 2019-03-17
+ * @since v0.2.0
*/
public static final class Builder {
/**
@@ -52,6 +53,7 @@ public final class ExpressionParser {
* would use {@code Integer::parseInt}.
*
* @since 2019-03-14
+ * @since v0.2.0
*/
private final Function objectObtainer;
@@ -59,6 +61,7 @@ public final class ExpressionParser {
* The function of the space as an operator (like 3 x y)
*
* @since 2019-03-22
+ * @since v0.2.0
*/
private String spaceFunction = null;
@@ -66,6 +69,7 @@ public final class ExpressionParser {
* A map mapping operator strings to operator functions, for unary operators.
*
* @since 2019-03-14
+ * @since v0.2.0
*/
private final Map> unaryOperators;
@@ -73,6 +77,7 @@ public final class ExpressionParser {
* A map mapping operator strings to operator functions, for binary operators.
*
* @since 2019-03-14
+ * @since v0.2.0
*/
private final Map> binaryOperators;
@@ -84,6 +89,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if {@code objectObtainer} is null
* @since 2019-03-17
+ * @since v0.2.0
*/
public Builder(final Function objectObtainer) {
this.objectObtainer = Objects.requireNonNull(objectObtainer, "objectObtainer must not be null.");
@@ -104,6 +110,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if {@code text} or {@code operator} is null
* @since 2019-03-17
+ * @since v0.2.0
*/
public Builder addBinaryOperator(final String text, final BinaryOperator operator, final int priority) {
Objects.requireNonNull(text, "text must not be null.");
@@ -128,6 +135,7 @@ public final class ExpressionParser {
* text of operator to use
* @return this builder
* @since 2019-03-22
+ * @since v0.2.0
*/
public Builder addSpaceFunction(final String operator) {
Objects.requireNonNull(operator, "operator must not be null.");
@@ -152,6 +160,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if {@code text} or {@code operator} is null
* @since 2019-03-17
+ * @since v0.2.0
*/
public Builder addUnaryOperator(final String text, final UnaryOperator operator, final int priority) {
Objects.requireNonNull(text, "text must not be null.");
@@ -171,6 +180,7 @@ public final class ExpressionParser {
/**
* @return an {@code ExpressionParser} instance with the properties given to this builder
* @since 2019-03-17
+ * @since v0.2.0
*/
public ExpressionParser build() {
return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, this.binaryOperators,
@@ -185,11 +195,15 @@ public final class ExpressionParser {
* @param
* type of operand and result
* @since 2019-03-17
+ * @since v0.2.0
*/
private static abstract class PriorityBinaryOperator
implements BinaryOperator, Comparable> {
/**
* The operator's priority. Higher-priority operators are applied before lower-priority operators
+ *
+ * @since 2019-03-17
+ * @since v0.2.0
*/
private final int priority;
@@ -199,6 +213,7 @@ public final class ExpressionParser {
* @param priority
* operator's priority
* @since 2019-03-17
+ * @since v0.2.0
*/
public PriorityBinaryOperator(final int priority) {
this.priority = priority;
@@ -209,6 +224,10 @@ public final class ExpressionParser {
*
*
* {@inheritDoc}
+ *
+ *
+ * @since 2019-03-17
+ * @since v0.2.0
*/
@Override
public int compareTo(final PriorityBinaryOperator o) {
@@ -223,6 +242,7 @@ public final class ExpressionParser {
/**
* @return priority
* @since 2019-03-22
+ * @since v0.2.0
*/
public final int getPriority() {
return this.priority;
@@ -236,11 +256,15 @@ public final class ExpressionParser {
* @param
* type of operand and result
* @since 2019-03-17
+ * @since v0.2.0
*/
private static abstract class PriorityUnaryOperator
implements UnaryOperator, Comparable> {
/**
* The operator's priority. Higher-priority operators are applied before lower-priority operators
+ *
+ * @since 2019-03-17
+ * @since v0.2.0
*/
private final int priority;
@@ -250,6 +274,7 @@ public final class ExpressionParser {
* @param priority
* operator's priority
* @since 2019-03-17
+ * @since v0.2.0
*/
public PriorityUnaryOperator(final int priority) {
this.priority = priority;
@@ -260,6 +285,10 @@ public final class ExpressionParser {
*
*
* {@inheritDoc}
+ *
+ *
+ * @since 2019-03-17
+ * @since v0.2.0
*/
@Override
public int compareTo(final PriorityUnaryOperator o) {
@@ -274,6 +303,7 @@ public final class ExpressionParser {
/**
* @return priority
* @since 2019-03-22
+ * @since v0.2.0
*/
public final int getPriority() {
return this.priority;
@@ -285,6 +315,7 @@ public final class ExpressionParser {
*
* @author Adrien Hopkins
* @since 2019-03-14
+ * @since v0.2.0
*/
private static enum TokenType {
OBJECT, UNARY_OPERATOR, BINARY_OPERATOR;
@@ -294,6 +325,7 @@ public final class ExpressionParser {
* The opening bracket.
*
* @since 2019-03-22
+ * @since v0.2.0
*/
public static final char OPENING_BRACKET = '(';
@@ -301,6 +333,7 @@ public final class ExpressionParser {
* The closing bracket.
*
* @since 2019-03-22
+ * @since v0.2.0
*/
public static final char CLOSING_BRACKET = ')';
@@ -315,6 +348,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if string is null
* @since 2019-03-22
+ * @since v0.2.0
*/
private static int findBracketPair(final String string, final int bracketPosition) {
Objects.requireNonNull(string, "string must not be null.");
@@ -361,6 +395,7 @@ public final class ExpressionParser {
* use {@code Integer::parseInt}.
*
* @since 2019-03-14
+ * @since v0.2.0
*/
private final Function objectObtainer;
@@ -368,6 +403,7 @@ public final class ExpressionParser {
* A map mapping operator strings to operator functions, for unary operators.
*
* @since 2019-03-14
+ * @since v0.2.0
*/
private final Map> unaryOperators;
@@ -375,6 +411,7 @@ public final class ExpressionParser {
* A map mapping operator strings to operator functions, for binary operators.
*
* @since 2019-03-14
+ * @since v0.2.0
*/
private final Map> binaryOperators;
@@ -382,6 +419,7 @@ public final class ExpressionParser {
* The operator for space, or null if spaces have no function.
*
* @since 2019-03-22
+ * @since v0.2.0
*/
private final String spaceOperator;
@@ -397,6 +435,7 @@ public final class ExpressionParser {
* @param spaceOperator
* operator used by spaces
* @since 2019-03-14
+ * @since v0.2.0
*/
private ExpressionParser(final Function objectObtainer,
final Map> unaryOperators,
@@ -419,6 +458,7 @@ public final class ExpressionParser {
* expression
* @return expression in RPN
* @since 2019-03-17
+ * @since v0.2.0
*/
private String convertExpressionToReversePolish(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
@@ -523,6 +563,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if components is null
* @since 2019-03-22
+ * @since v0.2.0
*/
private int findHighestPriorityOperatorPosition(final List components) {
Objects.requireNonNull(components, "components must not be null.");
@@ -572,6 +613,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if {@code expression} is null
* @since 2019-03-14
+ * @since v0.2.0
*/
private TokenType getTokenType(final String token) {
Objects.requireNonNull(token, "token must not be null.");
@@ -593,6 +635,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if {@code expression} is null
* @since 2019-03-14
+ * @since v0.2.0
*/
public T parseExpression(final String expression) {
return this.parseReversePolishExpression(this.convertExpressionToReversePolish(expression));
@@ -607,6 +650,7 @@ public final class ExpressionParser {
* @throws NullPointerException
* if {@code expression} is null
* @since 2019-03-14
+ * @since v0.2.0
*/
private T parseReversePolishExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
diff --git a/src/org/unitConverter/unit/AbstractUnit.java b/src/org/unitConverter/unit/AbstractUnit.java
index a0d6f7e..05a6c17 100644
--- a/src/org/unitConverter/unit/AbstractUnit.java
+++ b/src/org/unitConverter/unit/AbstractUnit.java
@@ -110,7 +110,6 @@ public abstract class AbstractUnit implements Unit {
return this.system;
}
- // TODO document and revise units' toString methods
@Override
public String toString() {
return String.format("%s-derived unit of dimension %s", this.getSystem(), this.getDimension());
diff --git a/src/org/unitConverter/unit/BaseUnit.java b/src/org/unitConverter/unit/BaseUnit.java
index 8bac866..67309cf 100755
--- a/src/org/unitConverter/unit/BaseUnit.java
+++ b/src/org/unitConverter/unit/BaseUnit.java
@@ -111,6 +111,7 @@ public final class BaseUnit extends LinearUnit {
/**
* @return true if the unit is a "full base" unit like the metre or second.
* @since 2019-04-10
+ * @since v0.2.0
*/
public final boolean isFullBase() {
return this.isFullBase;
diff --git a/src/org/unitConverter/unit/DefaultUnitPrefix.java b/src/org/unitConverter/unit/DefaultUnitPrefix.java
index c0e8dcc..4a9e487 100755
--- a/src/org/unitConverter/unit/DefaultUnitPrefix.java
+++ b/src/org/unitConverter/unit/DefaultUnitPrefix.java
@@ -33,6 +33,7 @@ public final class DefaultUnitPrefix implements UnitPrefix {
*
* @param multiplier
* @since 2019-01-14
+ * @since v0.2.0
*/
public DefaultUnitPrefix(final double multiplier) {
this.multiplier = multiplier;
diff --git a/src/org/unitConverter/unit/LinearUnit.java b/src/org/unitConverter/unit/LinearUnit.java
index 5b2680b..1b1ac97 100644
--- a/src/org/unitConverter/unit/LinearUnit.java
+++ b/src/org/unitConverter/unit/LinearUnit.java
@@ -175,6 +175,7 @@ public class LinearUnit extends AbstractUnit {
* @throws NullPointerException
* if {@code subtrahend} is null
* @since 2019-03-17
+ * @since v0.2.0
*/
public LinearUnit minus(final LinearUnit subtrahendend) {
Objects.requireNonNull(subtrahendend, "addend must not be null.");
@@ -203,6 +204,7 @@ public class LinearUnit extends AbstractUnit {
* @throws NullPointerException
* if {@code addend} is null
* @since 2019-03-17
+ * @since v0.2.0
*/
public LinearUnit plus(final LinearUnit addend) {
Objects.requireNonNull(addend, "addend must not be null.");
@@ -284,6 +286,7 @@ public class LinearUnit extends AbstractUnit {
* prefix to apply
* @return unit with prefix
* @since 2019-03-18
+ * @since v0.2.0
*/
public LinearUnit withPrefix(final UnitPrefix prefix) {
return this.times(prefix.getMultiplier());
diff --git a/src/org/unitConverter/unit/UnitPrefix.java b/src/org/unitConverter/unit/UnitPrefix.java
index a1609c6..9f9645d 100755
--- a/src/org/unitConverter/unit/UnitPrefix.java
+++ b/src/org/unitConverter/unit/UnitPrefix.java
@@ -31,6 +31,7 @@ public interface UnitPrefix {
* prefix to divide by
* @return quotient of prefixes
* @since 2019-04-13
+ * @since v0.2.0
*/
default UnitPrefix dividedBy(final UnitPrefix other) {
return new DefaultUnitPrefix(this.getMultiplier() / other.getMultiplier());
@@ -50,6 +51,7 @@ public interface UnitPrefix {
* prefix to multiply by
* @return product of prefixes
* @since 2019-04-13
+ * @since v0.2.0
*/
default UnitPrefix times(final UnitPrefix other) {
return new DefaultUnitPrefix(this.getMultiplier() * other.getMultiplier());
@@ -62,6 +64,7 @@ public interface UnitPrefix {
* exponent to raise to
* @return result of exponentiation.
* @since 2019-04-13
+ * @since v0.2.0
*/
default UnitPrefix toExponent(final double exponent) {
return new DefaultUnitPrefix(Math.pow(getMultiplier(), exponent));
diff --git a/src/org/unitConverter/unit/package-info.java b/src/org/unitConverter/unit/package-info.java
index c4493ae..dd5a939 100644
--- a/src/org/unitConverter/unit/package-info.java
+++ b/src/org/unitConverter/unit/package-info.java
@@ -19,5 +19,6 @@
*
* @author Adrien Hopkins
* @since 2019-01-25
+ * @since v0.1.0
*/
package org.unitConverter.unit;
\ No newline at end of file
diff --git a/src/test/java/ExpressionParserTest.java b/src/test/java/ExpressionParserTest.java
index 62fa964..40c91ac 100644
--- a/src/test/java/ExpressionParserTest.java
+++ b/src/test/java/ExpressionParserTest.java
@@ -26,6 +26,7 @@ import org.unitConverter.math.ExpressionParser;
*
* @author Adrien Hopkins
* @since 2019-03-22
+ * @since v0.2.0
*/
public class ExpressionParserTest {
private static final ExpressionParser numberParser = new ExpressionParser.Builder<>(Integer::parseInt)
diff --git a/src/test/java/UnitTest.java b/src/test/java/UnitTest.java
index 952b6f2..00fcf3c 100755
--- a/src/test/java/UnitTest.java
+++ b/src/test/java/UnitTest.java
@@ -35,6 +35,7 @@ import org.unitConverter.unit.Unit;
*
* @author Adrien Hopkins
* @since 2018-12-22
+ * @since v0.1.0
*/
public class UnitTest {
/** A random number generator */
diff --git a/src/test/java/UnitsDatabaseTest.java b/src/test/java/UnitsDatabaseTest.java
index 8429561..9222740 100644
--- a/src/test/java/UnitsDatabaseTest.java
+++ b/src/test/java/UnitsDatabaseTest.java
@@ -38,6 +38,7 @@ import org.unitConverter.unit.UnitPrefix;
*
* @author Adrien Hopkins
* @since 2019-04-14
+ * @since v0.2.0
*/
public class UnitsDatabaseTest {
// some linear units and one nonlinear
@@ -72,6 +73,7 @@ public class UnitsDatabaseTest {
* Test that prefixes correctly apply to units.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
@Test
public void testPrefixes() {
@@ -101,6 +103,7 @@ public class UnitsDatabaseTest {
*
*
* @since 2019-04-14
+ * @since v0.2.0
*/
@Test
public void testPrefixlessUnitMap() {
@@ -123,6 +126,7 @@ public class UnitsDatabaseTest {
* Tests that the database correctly stores and retrieves units, ignoring prefixes.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
@Test
public void testPrefixlessUnits() {
@@ -143,6 +147,7 @@ public class UnitsDatabaseTest {
* Test that unit expressions return the correct value.
*
* @since 2019-04-14
+ * @since v0.2.0
*/
@Test
public void testUnitExpressions() {
@@ -176,6 +181,7 @@ public class UnitsDatabaseTest {
* Tests both the unit name iterator and the name-unit entry iterator
*
* @since 2019-04-14
+ * @since v0.2.0
*/
@Test
public void testUnitIterator() {
@@ -221,6 +227,7 @@ public class UnitsDatabaseTest {
*
*
* @since 2019-04-14
+ * @since v0.2.0
*/
@Test
public void testUnitPrefixCombinations() {
diff --git a/src/test/java/package-info.java b/src/test/java/package-info.java
index 87b4a06..3da7fcb 100644
--- a/src/test/java/package-info.java
+++ b/src/test/java/package-info.java
@@ -19,5 +19,6 @@
*
* @author Adrien Hopkins
* @since 2019-03-16
+ * @since v0.2.0
*/
package test.java;
\ No newline at end of file
--
cgit v1.2.3
From b43c399c21bddd3cb8af42c109940564c3890cf7 Mon Sep 17 00:00:00 2001
From: Adrien Hopkins
Date: Sun, 14 Apr 2019 17:41:10 -0400
Subject: Bumped the version number to v0.2.0
---
CHANGELOG.org | 4 ++--
README.org | 23 +++++++++++++----------
src/org/unitConverter/package-info.java | 1 +
3 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.org b/CHANGELOG.org
index db9766b..77e7593 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -1,7 +1,7 @@
* Changelog
All notable changes in this project will be shown in this file.
-** Unreleased
+** v0.2.0 - [2019-04-14]
*** Changed
- When searching for units, units with no prefixes are searched for before prefixed units
- Smaller prefixes are searched for before larger prefixes
@@ -17,7 +17,7 @@ All notable changes in this project will be shown in this file.
- You can now add and subtract in unit expressions!
- Instructions for obtaining unit instances are provided in the relevant classes
- The UnitPrefix interface now provides default times, dividedBy and toExponent methods.
-** v0.1.0
+** v0.1.0 - [2019-02-01]
NOTE: At this stage, the API is subject to significant change.
*** Added
- Unit interface, implemented and supporting classes
diff --git a/README.org b/README.org
index 5500d68..2a6fa95 100644
--- a/README.org
+++ b/README.org
@@ -1,25 +1,27 @@
-* What is it?
+* Unit Converter v0.2.0
+(this project uses Semantic Versioning)
+** What is it?
This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in.
-* Features
+** Features
- Convert between units and expressions of units
- linear or base unit can use unit prefixes (including non-metric units!)
- and prefixes are defined in an editable data file, in a simple and intuitive format.
- Viewer and Prefix Viewer which allow you to search through all of the available units and prefixes and learn details about them.
- All of SI included in default text file
- Choose your precision
-* How to Convert (in "Convert Units")
+** How to Convert (in "Convert Units")
To convert units, simply:
1. Select the kind of units to convert (length/mass/time, etc.).
2. Select the units to convert from and to (you can use the text boxes above to search)
3. Enter a value to convert
4. Press "Convert"
-* How to Convert (in "Convert Expressions")
+** How to Convert (in "Convert Expressions")
To convert units, simply enter the first unit in the From box and the second unit in the To box. Press Convert and a result will appear in the output box. Both boxes accept unit expressions, so you can input things like ‘1.7 m’, ‘85 km/h’ and ‘35 A * 14 s’ into either box.
*Warning*: The space between the number and the unit name is required, or else the whole expression will be interpreted as a unit name. Spaces are not required for operators.
Use the slider at the bottom to choose the maximum precision of the result, in significant digits, not decimal places.
-* Units Files and Expressions
+** Units Files and Expressions
As mentioned previously, all units are loaded from a units file. The format for lines consists of a name for the unit, some tabs (not spaces), and an expression. The following operations are supported in expressions:
- Addition using the plus sign (+), which only works on compatible units
- Subtraction using the minus sign (-), which only works on compatible units
@@ -44,14 +46,14 @@ Unit prefixes are defined differently. When a unit name ends with the dash (-)
- Division using the forward slash (/)
- Exponentiation using the caret (^)
Every argument can be a number or a prefix name, except exponents which much be integers
-* Unit Prefixes
+** Unit Prefixes
In SI, you can have a unit prefix, which you attach to the start of a unit and it multiplies the unit by a certain value. For example, milli-metre = 0.001 * metre. This unit converter supports unit prefixes, and you can put any number of prefixes before a linear or base unit to transform it (this includes non-SI units).
You can use more than one prefix at once (yottayottametre = really really long distance), but you may not convert prefixes alone. If you want to do that, use ‘unit’ or ‘u’ (ku = 1000000 mu). You can also use u for converting from numbers (pi = 3.141593 u).
The default unit file allows you to use D-, H- and K- in place of da-, h- and k- if you want.
*Warning*: The standard prefixes will never use 1024 instead of 1000, even when operating on bits and bytes. Use ‘Ki’, ‘Mi’, ‘Gi’, ‘Ti’ and so on instead.
-* Nonlinear Units
+** Nonlinear Units
Sometimes, units cannot be converted from and to by simply multiplying and dividing. A common example of this is the Celsius and Fahrenheit temperature scales, which require multiplication and addition to convert to each other (and to their base, Kelvin).
To use nonlinear units, use the following syntax:
@@ -64,9 +66,9 @@ Nonlinear units cannot:
- be defined by unit files
To define a nonlinear unit, make an anonymous inner type (or any other subclass) of AbstractUnit, and define the conversion methods.
-* Unit and Prefix Viewers
+** Unit and Prefix Viewers
The unit and prefix viewers can be used to see the available units (without prefixes) and prefixes. Upon opening them, you will see a list of units or prefixes on your left. Using the text box above, the list can be filtered. When a unit is clicked on, details about will be displayed on the right.
-* Copyright and Licences
+** Copyright and Licences
The Unit Converter program is Copyright (C) 2018, 2019 Adrien Hopkins. It is released under the terms of the Aferro GNU General Public License, version 3.0 or any later version published by the Free Software Foundation. A copy of this license should be provided with this program, and a human-readable summary of the very similar GNU General Public License can be found at the following link: https://www.gnu.org/licenses/quick-guide-gplv3.html, although this summary is NOT a replacement for the actual license.
This document is Copyright (C) 2019 Adrien Hopkins. This document is dual-licensed under the terms of the GNU Free Documentation License and the Creative Commons Attribution-ShareAlike License. More details are in the next paragraphs:
@@ -74,7 +76,7 @@ This document is Copyright (C) 2019 Adrien Hopkins. This document is dual-licen
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
-** GNU Free Documentation License
+*** GNU Free Documentation License
GNU Free Documentation License
Version 1.3, 3 November 2008
@@ -526,3 +528,4 @@ If your document contains nontrivial examples of program code, we
recommend releasing these examples in parallel under your choice of
free software license, such as the GNU General Public License,
to permit their use in free software.
+
diff --git a/src/org/unitConverter/package-info.java b/src/org/unitConverter/package-info.java
index 4f51ad0..23dd165 100644
--- a/src/org/unitConverter/package-info.java
+++ b/src/org/unitConverter/package-info.java
@@ -18,6 +18,7 @@
* A program that converts units.
*
* @author Adrien Hopkins
+ * @version v0.2.0
* @since 2019-01-25
*/
package org.unitConverter;
\ No newline at end of file
--
cgit v1.2.3