summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2024-03-24 13:25:22 -0500
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2024-03-24 13:25:22 -0500
commited53492243ecad8d975401a97f5b634328ad2c71 (patch)
tree8a744f46320710355a02c9b2c371602ce69aefec
parentc878761f737c90fc3fa1caedd48e2ee01637108f (diff)
parent91d51c3c49c4c0877483220ac0f12db4efab8f60 (diff)
Release version 0.5.0 (merge into stable)
-rw-r--r--.gitignore1
-rw-r--r--.settings/org.eclipse.core.resources.prefs2
-rw-r--r--.settings/org.eclipse.core.runtime.prefs2
-rw-r--r--CHANGELOG.org12
-rw-r--r--README.org2
-rw-r--r--docs/data_spec.org39
-rw-r--r--docs/data_spec.pdfbin0 -> 90604 bytes
-rw-r--r--docs/data_spec.tex74
-rw-r--r--docs/design.org8
-rw-r--r--docs/design.pdfbin346620 -> 352153 bytes
-rw-r--r--docs/design.tex78
-rw-r--r--docs/manual.org21
-rw-r--r--docs/manual.pdfbin170946 -> 185625 bytes
-rw-r--r--docs/manual.tex60
-rw-r--r--docs/roadmap.org22
-rw-r--r--src/main/java/sevenUnits/ProgramInfo.java12
-rw-r--r--src/main/java/sevenUnits/unit/BaseDimension.java8
-rw-r--r--src/main/java/sevenUnits/unit/BaseUnit.java18
-rw-r--r--src/main/java/sevenUnits/unit/BritishImperial.java18
-rw-r--r--src/main/java/sevenUnits/unit/FunctionalUnit.java56
-rw-r--r--src/main/java/sevenUnits/unit/FunctionalUnitlike.java10
-rw-r--r--src/main/java/sevenUnits/unit/LinearUnit.java72
-rw-r--r--src/main/java/sevenUnits/unit/LinearUnitValue.java66
-rw-r--r--src/main/java/sevenUnits/unit/Metric.java62
-rw-r--r--src/main/java/sevenUnits/unit/MultiUnit.java30
-rw-r--r--src/main/java/sevenUnits/unit/USCustomary.java30
-rw-r--r--src/main/java/sevenUnits/unit/Unit.java56
-rw-r--r--src/main/java/sevenUnits/unit/UnitDatabase.java486
-rw-r--r--src/main/java/sevenUnits/unit/UnitPrefix.java48
-rw-r--r--src/main/java/sevenUnits/unit/UnitType.java2
-rw-r--r--src/main/java/sevenUnits/unit/UnitValue.java26
-rw-r--r--src/main/java/sevenUnits/unit/Unitlike.java38
-rw-r--r--src/main/java/sevenUnits/unit/UnitlikeValue.java26
-rw-r--r--src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java104
-rw-r--r--src/main/java/sevenUnits/utils/DecimalComparison.java20
-rw-r--r--src/main/java/sevenUnits/utils/ExpressionParser.java308
-rw-r--r--src/main/java/sevenUnits/utils/NameSymbol.java44
-rw-r--r--src/main/java/sevenUnits/utils/Nameable.java10
-rw-r--r--src/main/java/sevenUnits/utils/ObjectProduct.java56
-rw-r--r--src/main/java/sevenUnits/utils/SemanticVersionNumber.java94
-rw-r--r--src/main/java/sevenUnits/utils/UncertainDouble.java116
-rw-r--r--src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java10
-rw-r--r--src/main/java/sevenUnitsGUI/DelegateListModel.java14
-rw-r--r--src/main/java/sevenUnitsGUI/ExpressionConversionView.java4
-rw-r--r--src/main/java/sevenUnitsGUI/FilterComparator.java14
-rw-r--r--src/main/java/sevenUnitsGUI/GridBagBuilder.java205
-rw-r--r--src/main/java/sevenUnitsGUI/Main.java4
-rw-r--r--src/main/java/sevenUnitsGUI/PrefixSearchRule.java26
-rw-r--r--src/main/java/sevenUnitsGUI/Presenter.java408
-rw-r--r--src/main/java/sevenUnitsGUI/SearchBoxList.java72
-rw-r--r--src/main/java/sevenUnitsGUI/StandardDisplayRules.java58
-rw-r--r--src/main/java/sevenUnitsGUI/TabbedView.java204
-rw-r--r--src/main/java/sevenUnitsGUI/UnitConversionRecord.java24
-rw-r--r--src/main/java/sevenUnitsGUI/UnitConversionView.java20
-rw-r--r--src/main/java/sevenUnitsGUI/View.java16
-rw-r--r--src/main/java/sevenUnitsGUI/ViewBot.java126
-rw-r--r--src/main/resources/about.txt11
-rw-r--r--src/test/java/sevenUnits/unit/MultiUnitTest.java26
-rw-r--r--src/test/java/sevenUnits/unit/UnitDatabaseTest.java196
-rw-r--r--src/test/java/sevenUnits/unit/UnitTest.java72
-rw-r--r--src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java26
-rw-r--r--src/test/java/sevenUnits/utils/ExpressionParserTest.java19
-rw-r--r--src/test/java/sevenUnits/utils/ObjectProductTest.java3
-rw-r--r--src/test/java/sevenUnits/utils/SemanticVersionTest.java56
-rw-r--r--src/test/java/sevenUnits/utils/UncertainDoubleTest.java28
-rw-r--r--src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java10
-rw-r--r--src/test/java/sevenUnitsGUI/PrefixSearchTest.java22
-rw-r--r--src/test/java/sevenUnitsGUI/PresenterTest.java98
-rw-r--r--src/test/java/sevenUnitsGUI/RoundingTest.java36
-rw-r--r--src/test/java/sevenUnitsGUI/TabbedViewTest.java22
-rw-r--r--src/test/resources/test-dimensionfile-valid1.txt8
71 files changed, 2242 insertions, 1733 deletions
diff --git a/.gitignore b/.gitignore
index bbda9ac..45bd4fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,5 @@ build
# 7Units gitignore files
*.class
*~
-settings.txt
/src/test/resources/test-settings.txt
/bin/
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
index 9bca13f..99f26c0 100644
--- a/.settings/org.eclipse.core.resources.prefs
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -1,2 +1,2 @@
eclipse.preferences.version=1
-encoding/settings.txt=UTF-8
+encoding/<project>=UTF-8
diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..5a0ad22
--- /dev/null
+++ b/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 18951c1..9696497 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -1,6 +1,16 @@
* Changelog
All notable changes in this project will be shown in this file.
-** v0.4.1 - [2023-04-15]
+** v0.5.0 - [2024-03-24 Sun]
+*** Added
+- *Added specifications for all types data files used by 7Units.*
+*** Changed
+- Configuration file is now located at a standard location in the system, instead of being in the project directory.
+- Simplified some setting values in the config file
+- Made the various data files more consistent with each other:
+ - Addition and subtraction may be used in prefix expressions.
+ - Exponentation works for all bases in dimension expressions.
+ - All types of config files support comments.
+** v0.4.1 - [2023-04-15 Sat]
*** Fixed
- Upgrade Gradle from 6.3 to 8.1 to support users of new Java versions
(though 7Units still uses Java 11, the old Gradle distribution would fail if compiled with a newer version of Java)
diff --git a/README.org b/README.org
index d48ccb4..4e64591 100644
--- a/README.org
+++ b/README.org
@@ -1,4 +1,4 @@
-* 7Units Version 0.4.1
+* 7Units Version 0.5.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.
diff --git a/docs/data_spec.org b/docs/data_spec.org
new file mode 100644
index 0000000..c75eaa7
--- /dev/null
+++ b/docs/data_spec.org
@@ -0,0 +1,39 @@
+#+TITLE: 7Units Datafile Specification
+#+SUBTITLE: For Version 0.5.0
+#+DATE: 2024 March 23
+#+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry}
+
+#+LaTeX: \newpage
+* Unit and Prefix Files
+Unit and prefix files specify the units and prefixes that are available to 7Units. Their format is intended to be compatible with 7Units's inspiration, [[https://www.gnu.org/software/units/][GNU Units]].
+
+Each unit or prefix is specified by a line in the following format:
+#+BEGIN_SRC
+<name><one or more whitespace><definition>
+#+END_SRC
+
+The name may not contain whitespace, but the definition can and often will. Lines can be separated by either a line feed (0x0A) or a carriage return followed by a line feed (0x0D 0x0A).
+** Ignored Lines & Comments
+All of the following should be ignored:
+- Any line containing only whitespace
+- Any trailing whitespace
+- Any characters between a ~#~ (0x23) character and the end of the line.
+
+This allows unit and prefix files to be easily organized and divided.
+** Unit Expressions
+The definition part of a unit's line is a unit expression - the same sort of expression you would use in the complex unit converter.
+
+Expressions should be a standard mathematical expression, which can operate on either numbers or units. The following operators are supported: addition (+), subtraction (-), multiplication (~*~ or no operator), division (~/~ or ~|~), exponentation (~^~; exponent must be a number). Brackets (~(~ and ~)~) may be used to change order of operations, but otherwise standard BEDMAS order is followed (exponentation first, then multiplication and division, then addition and subtraction), with two exceptions: if a number is multiplied by a unit using spaces, the multiplication will have precedence over division, and division with ~|~ has higher precedence than any other operator. For example, "2 m / 1 m" is equal to the dimensionless value 2, not 2 m^2. An example of a line defining a unit is:
+#+BEGIN_SRC
+yard 9 dm + 1.4 cm + 4 mm^2 / 10 mm
+#+END_SRC
+
+If the definition is an exclamation mark (~!~), this defines a base unit, which is expected to already be stored in the system.
+** Prefixes & Prefix Expressions
+If a line's name part ends in the ASCII dash (~-~, 0x2D), it defines a prefix instead of a unit (this dash is not included in the prefix's name). Prefix expressions are like unit expressions, but the operands are prefixes and numbers, not units and numbers. Because prefixes do not have a dimension, there are no base prefixes - the exclamation mark is forbidden in prefix expressions.
+* Dimension Files
+Dimension files give names to unit dimensions, so they can be selected in the unit converter to determine which units are shown. Dimension files are similar to unit and prefix files, except that they use a different set of base /dimensions/ (defined with ! as usual), and that addition and subtraction are not supported in dimension expressions.
+* Metric Exception Files
+Metric exception files list exceptions to the One-Way Conversion rule. Units included in these files are always shown on both sides of the unit converter, even if One Way Conversion is enabled. They are just a list of units, one per line. Ignored lines and comments work the same way as in other data files.
+* Configuration Files
+A configuration file contains one line for each configuration setting, in the format ~key=value~. Check the user manual for the list of configurable setting keys. Ignored lines and comments work the same way as in other data files.
diff --git a/docs/data_spec.pdf b/docs/data_spec.pdf
new file mode 100644
index 0000000..35e01f8
--- /dev/null
+++ b/docs/data_spec.pdf
Binary files differ
diff --git a/docs/data_spec.tex b/docs/data_spec.tex
new file mode 100644
index 0000000..879d62c
--- /dev/null
+++ b/docs/data_spec.tex
@@ -0,0 +1,74 @@
+% Created 2024-03-24 Sun 13:16
+% Intended LaTeX compiler: pdflatex
+\documentclass[11pt]{article}
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage{graphicx}
+\usepackage{longtable}
+\usepackage{wrapfig}
+\usepackage{rotating}
+\usepackage[normalem]{ulem}
+\usepackage{amsmath}
+\usepackage{amssymb}
+\usepackage{capt-of}
+\usepackage{hyperref}
+\usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry}
+\date{2024 March 23}
+\title{7Units Datafile Specification\\\medskip
+\large For Version 0.5.0}
+\hypersetup{
+ pdfauthor={},
+ pdftitle={7Units Datafile Specification},
+ pdfkeywords={},
+ pdfsubject={},
+ pdfcreator={Emacs 29.2 (Org mode 9.6.15)},
+ pdflang={English}}
+\begin{document}
+
+\maketitle
+\tableofcontents
+
+\newpage
+\section{Unit and Prefix Files}
+\label{sec:org3f0bfdf}
+Unit and prefix files specify the units and prefixes that are available to 7Units. Their format is intended to be compatible with 7Units's inspiration, \href{https://www.gnu.org/software/units/}{GNU Units}.
+
+Each unit or prefix is specified by a line in the following format:
+\begin{verbatim}
+<name><one or more whitespace><definition>
+\end{verbatim}
+
+The name may not contain whitespace, but the definition can and often will. Lines can be separated by either a line feed (0x0A) or a carriage return followed by a line feed (0x0D 0x0A).
+\subsection{Ignored Lines \& Comments}
+\label{sec:org7396c6b}
+All of the following should be ignored:
+\begin{itemize}
+\item Any line containing only whitespace
+\item Any trailing whitespace
+\item Any characters between a \texttt{\#} (0x23) character and the end of the line.
+\end{itemize}
+
+This allows unit and prefix files to be easily organized and divided.
+\subsection{Unit Expressions}
+\label{sec:orga56737c}
+The definition part of a unit's line is a unit expression - the same sort of expression you would use in the complex unit converter.
+
+Expressions should be a standard mathematical expression, which can operate on either numbers or units. The following operators are supported: addition (+), subtraction (-), multiplication (\texttt{*} or no operator), division (\texttt{/} or \texttt{|}), exponentation (\texttt{\textasciicircum{}}; exponent must be a number). Brackets (\texttt{(} and \texttt{)}) may be used to change order of operations, but otherwise standard BEDMAS order is followed (exponentation first, then multiplication and division, then addition and subtraction), with two exceptions: if a number is multiplied by a unit using spaces, the multiplication will have precedence over division, and division with \texttt{|} has higher precedence than any other operator. For example, "2 m / 1 m" is equal to the dimensionless value 2, not 2 m\textsuperscript{2}. An example of a line defining a unit is:
+\begin{verbatim}
+yard 9 dm + 1.4 cm + 4 mm^2 / 10 mm
+\end{verbatim}
+
+If the definition is an exclamation mark (\texttt{!}), this defines a base unit, which is expected to already be stored in the system.
+\subsection{Prefixes \& Prefix Expressions}
+\label{sec:org28c98b9}
+If a line's name part ends in the ASCII dash (\texttt{-}, 0x2D), it defines a prefix instead of a unit (this dash is not included in the prefix's name). Prefix expressions are like unit expressions, but the operands are prefixes and numbers, not units and numbers. Because prefixes do not have a dimension, there are no base prefixes - the exclamation mark is forbidden in prefix expressions.
+\section{Dimension Files}
+\label{sec:org9530301}
+Dimension files give names to unit dimensions, so they can be selected in the unit converter to determine which units are shown. Dimension files are similar to unit and prefix files, except that they use a different set of base \emph{dimensions} (defined with ! as usual), and that addition and subtraction are not supported in dimension expressions.
+\section{Metric Exception Files}
+\label{sec:org79afe10}
+Metric exception files list exceptions to the One-Way Conversion rule. Units included in these files are always shown on both sides of the unit converter, even if One Way Conversion is enabled. They are just a list of units, one per line. Ignored lines and comments work the same way as in other data files.
+\section{Configuration Files}
+\label{sec:orgf3f6b9a}
+A configuration file contains one line for each configuration setting, in the format \texttt{key=value}. Check the user manual for the list of configurable setting keys. Ignored lines and comments work the same way as in other data files.
+\end{document}
diff --git a/docs/design.org b/docs/design.org
index 0e3a477..65ee651 100644
--- a/docs/design.org
+++ b/docs/design.org
@@ -1,6 +1,6 @@
#+TITLE: 7Units Design Document
-#+SUBTITLE: For version 0.4.0
-#+DATE: 2022 July 8
+#+SUBTITLE: For version 0.5.0
+#+DATE: 2024 March 23
#+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry}
#+LaTeX_HEADER: \usepackage{xurl}
#+LaTeX: \newpage
@@ -132,12 +132,16 @@
~ExpressionParser~ has a parameterized type ~T~, which represents the type of the value used in the expression. The expression parser currently only supports one type of value per expression; in the expressions used by 7Units numbers are treated as a kind of unit or prefix. Operators are represented by internal types; the system distinguishes between unary operators (those that take a single value, like negation) and binary operators (those that take 2 values, like +, -, * or /).
+ There is one exception to this rule - users are allowed to create "numeric operators" that take one argument of type ~T~ and one numeric argument. This is intended for exponentation, but could also be used for vector scaling.
+
Expressions are parsed in 2 steps:
1. Convert the expression to [[https://en.wikipedia.org/wiki/Reverse_Polish_notation][Reverse Polish Notation]], where operators come *after* the values they operate on, and brackets and the order of operations are not necessary. For example, "2 + 5" becomes "~2 5 +~", "(1 + 2) * 3" becomes "~1 2 + 3 *~" and the example expression earlier becomes "~2 m * 30 J * N / + 8 s * *~". This makes it simple to evaluate - early calculators used RPN for a good reason!
2. Evaluate the RPN expression. This can be done simply with a for loop and a stack. For each token in the expression, the progam does the following:
- if it is a number or unit, add it to the stack.
+ (the dimension parser adds numbers to a separate stack for type safety, as numbers cannot be stored as a dimension type like they can with units.)
- if it is a unary operator, take one value from the stack, apply the operator to it, and put the result into the stack.
- if it is a binary operator, take two values from the stack, apply the operator to them, and put the result into the stack.
+ - if it is a numeric operator, take one value from the stack and one number from the numeric stack, apply to the operator to the two, and put the result in the regular stack.
After evaluating the last token, there should be one value left in the stack - the answer. If there isn't, the original expression was malformed.
** Math Classes
There are two simple math classes in 7Units:
diff --git a/docs/design.pdf b/docs/design.pdf
index 936928a..00703d8 100644
--- a/docs/design.pdf
+++ b/docs/design.pdf
Binary files differ
diff --git a/docs/design.tex b/docs/design.tex
index 35d2004..c850412 100644
--- a/docs/design.tex
+++ b/docs/design.tex
@@ -1,30 +1,28 @@
-% Created 2022-07-17 Sun 16:22
+% Created 2024-03-24 Sun 13:15
% Intended LaTeX compiler: pdflatex
\documentclass[11pt]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{graphicx}
-\usepackage{grffile}
\usepackage{longtable}
\usepackage{wrapfig}
\usepackage{rotating}
\usepackage[normalem]{ulem}
\usepackage{amsmath}
-\usepackage{textcomp}
\usepackage{amssymb}
\usepackage{capt-of}
\usepackage{hyperref}
\usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry}
\usepackage{xurl}
-\date{2022 July 8}
+\date{2024 March 23}
\title{7Units Design Document\\\medskip
-\large For version 0.4.0}
+\large For version 0.5.0}
\hypersetup{
pdfauthor={},
pdftitle={7Units Design Document},
pdfkeywords={},
pdfsubject={},
- pdfcreator={Emacs 27.1 (Org mode 9.4.6)},
+ pdfcreator={Emacs 29.2 (Org mode 9.6.15)},
pdflang={English}}
\begin{document}
@@ -34,35 +32,35 @@
\newpage
\section{Introduction}
-\label{sec:orgb154108}
+\label{sec:orgdac8c13}
7Units is a program that can convert between units. This document details the internal design of 7Units, intended to be used by current and future developers.
\section{System Overview}
-\label{sec:org064d7bc}
+\label{sec:org2a8e77a}
\begin{figure}[htbp]
\centering
\includegraphics[height=144px]{./diagrams/overview-diagram.plantuml.png}
\caption{A big-picture diagram of 7Units, containing all of the major classes.}
\end{figure}
\subsection{Packages of 7Units}
-\label{sec:org8a6985b}
+\label{sec:org80ac84e}
7Units splits its code into three main packages:
\begin{description}
-\item[{\texttt{sevenUnits.unit}}] The \hyperref[sec:org76c2c26]{unit system}
+\item[{\texttt{sevenUnits.unit}}] The \hyperref[sec:orgc2400d6]{unit system}
\item[{\texttt{sevenUnits.utils}}] Extra classes that aid the unit system.
-\item[{\texttt{sevenUnitsGUI}}] The \hyperref[sec:orgbb1d346]{front end} code
+\item[{\texttt{sevenUnitsGUI}}] The \hyperref[sec:org261b06e]{front end} code
\end{description}
\texttt{sevenUnits.unit} depends on \texttt{sevenUnits.utils}, while \texttt{sevenUnitsGUI} depends on both \texttt{sevenUnits} packages. There is only one class that isn't in any of these packages, \texttt{sevenUnits.VersionInfo}.
\subsection{Major Classes of 7Units}
-\label{sec:org031cf3b}
+\label{sec:org5910307}
\begin{description}
-\item[{\hyperref[sec:org195924f]{sevenUnits.unit.Unit}}] The class representing a unit
-\item[{\hyperref[sec:org1a286b2]{sevenUnits.unit.UnitDatabase}}] A class that stores collections of units, prefixes and dimensions.
-\item[{\hyperref[sec:orgae93ac0]{sevenUnitsGUI.View}}] The class that handles interaction between the user and the program.
-\item[{\hyperref[sec:org76432e4]{sevenUnitsGUI.Presenter}}] The class that handles communication between the \texttt{View} and the unit system.
+\item[{\hyperref[sec:org946a4e5]{sevenUnits.unit.Unit}}] The class representing a unit
+\item[{\hyperref[sec:orgac71770]{sevenUnits.unit.UnitDatabase}}] A class that stores collections of units, prefixes and dimensions.
+\item[{\hyperref[sec:org57b8a42]{sevenUnitsGUI.View}}] The class that handles interaction between the user and the program.
+\item[{\hyperref[sec:orga668171]{sevenUnitsGUI.Presenter}}] The class that handles communication between the \texttt{View} and the unit system.
\end{description}
\newpage
\subsection{Process of Unit Conversion}
-\label{sec:org0699189}
+\label{sec:orgbbad9d5}
\begin{figure}[htbp]
\centering
\includegraphics[width=.9\linewidth]{./diagrams/convert-units.plantuml.png}
@@ -77,7 +75,7 @@
\end{enumerate}
\newpage
\subsection{Process of Expression Conversion}
-\label{sec:orgbdb2960}
+\label{sec:org52749d5}
The process of expression conversion is similar to that of unit conversion.
\begin{figure}[htbp]
\centering
@@ -93,7 +91,7 @@ The process of expression conversion is similar to that of unit conversion.
\end{enumerate}
\newpage
\section{Unit System Design}
-\label{sec:org76c2c26}
+\label{sec:orgc2400d6}
Any code related to the backend unit system is stored in the \texttt{sevenUnits.unit} package.
Here is a class diagram of the system. Unimportant methods, methods inherited from Object, getters and setters have been omitted.
@@ -104,11 +102,11 @@ Here is a class diagram of the system. Unimportant methods, methods inherited f
\end{figure}
\newpage
\subsection{Dimensions}
-\label{sec:orgc8f3222}
-Dimensions represent what a unit is measuring, such as length, time, or energy. Dimensions are represented as an \hyperref[sec:org07580ff]{ObjectProduct}<BaseDimension>, where \texttt{BaseDimension} is a very simple class (its only properties are a name and a symbol) which represents the dimension of a base unit; these base dimensions can be multiplied to create all other Dimensions.
+\label{sec:orgb21aaed}
+Dimensions represent what a unit is measuring, such as length, time, or energy. Dimensions are represented as an \hyperref[sec:org16bc96f]{ObjectProduct}<BaseDimension>, where \texttt{BaseDimension} is a very simple class (its only properties are a name and a symbol) which represents the dimension of a base unit; these base dimensions can be multiplied to create all other Dimensions.
\subsection{Unit Classes}
-\label{sec:org195924f}
-Units are internally represented by the abstract class \texttt{Unit}. All units have an \hyperref[sec:org07580ff]{ObjectProduct}<BaseUnit> (referred to as the base) that they are based on, a dimension (ObjectProduct<BaseDimension>), one or more names and a symbol (these last two bits of data are contained in the \texttt{NameSymbol} class). The dimension is calculated from the base unit when needed; the variable is just a cache. It has two constructors: a package-private one used to make \texttt{BaseUnit} instances, and a protected one used to make general units (for other subclasses of \texttt{Unit}). All unit classes are immutable.
+\label{sec:org946a4e5}
+Units are internally represented by the abstract class \texttt{Unit}. All units have an \hyperref[sec:org16bc96f]{ObjectProduct}<BaseUnit> (referred to as the base) that they are based on, a dimension (ObjectProduct<BaseDimension>), one or more names and a symbol (these last two bits of data are contained in the \texttt{NameSymbol} class). The dimension is calculated from the base unit when needed; the variable is just a cache. It has two constructors: a package-private one used to make \texttt{BaseUnit} instances, and a protected one used to make general units (for other subclasses of \texttt{Unit}). All unit classes are immutable.
Units also have two conversion functions - one which converts from a value expressed in this unit to its base unit, and another which converts from a value expressed in the base unit to this unit. In \texttt{Unit}, they are defined as two abstract methods. This allows you to convert from any unit to any other (as long as they have the same base, i.e. you aren't converting metres to pounds). To convert from A to B, first convert from A to its base, then convert from the base to B.
@@ -133,20 +131,20 @@ There are a few more classes which play small roles in the unit system:
\item[{USCustomary}] A static utility class with instances of common units in the US Customary system (not to be confused with the British Imperial system; it has the same unit names but the values of a few units are different).
\end{description}
\subsection{Prefixes}
-\label{sec:org1504786}
+\label{sec:orgede1b85}
A \texttt{UnitPrefix} is a simple object that can multiply a \texttt{LinearUnit} by a value. It can calculate a new name for the unit by combining its name and the unit's name (symbols are done similarly). It can do multiplication, division and exponentation with a number, as well as multiplication and division with another prefix; all of these work by changing the prefix's multiplier.
\subsection{The Unit Database}
-\label{sec:org1a286b2}
+\label{sec:orgac71770}
The \texttt{UnitDatabase} class stores all of the unit, prefix and dimension data used by this program. It is not a representation of an actual database, just a class that stores lots of data.
Units are stored using a custom \texttt{Map} implementation (\texttt{PrefixedUnitMap}) which maps unit names to units. It is backed by two maps: one for units (without prefixes) and one for prefixes. It is programmed to include prefixes (so if units includes "metre" and prefixes includes "kilo", this map will include "kilometre", mapping it to a unit representing a kilometre). It is immutable, but you can modify the underlying maps, which is reflected in the \texttt{PrefixedUnitMap}. Other than that, it is a normal map implementation.
Prefixes and dimensions are stored in normal maps.
\subsubsection{Parsing Expressions}
-\label{sec:org3608cd5}
-Each \texttt{UnitDatabase} instance has four \hyperref[sec:orgb075c07]{ExpressionParser} instances associated with it, for four types of expressions: unit, unit value, prefix and dimension. They are mostly similar, with operators corresponding to each operation of the corresponding class (\texttt{LinearUnit}, \texttt{LinearUnitValue}, \texttt{UnitPrefix}, \texttt{ObjectProduct<BaseDimension>}). Unit and unit value expressions use linear units; nonlinear units can be used with a special syntax (like "degC(20)") and are immediately converted to a linear unit representing their base (Kelvin in this case) before operating.
+\label{sec:org02e3ff1}
+Each \texttt{UnitDatabase} instance has four \hyperref[sec:org7296a14]{ExpressionParser} instances associated with it, for four types of expressions: unit, unit value, prefix and dimension. They are mostly similar, with operators corresponding to each operation of the corresponding class (\texttt{LinearUnit}, \texttt{LinearUnitValue}, \texttt{UnitPrefix}, \texttt{ObjectProduct<BaseDimension>}). Unit and unit value expressions use linear units; nonlinear units can be used with a special syntax (like "degC(20)") and are immediately converted to a linear unit representing their base (Kelvin in this case) before operating.
\subsubsection{Parsing Files}
-\label{sec:org262b0a7}
+\label{sec:org676148d}
There are two types of data files: unit and dimension.
Unit files contain data about units and prefixes. Each line contains the name of a unit or prefix (prefixes end in a dash, units don't) followed by an expression which defines it, separated by one or more space characters (this behaviour is defined by the static regular expression \texttt{NAME\_EXPRESSION}). Unit files are parsed line by line, each line being run through the \texttt{addUnitOrPrefixFromLine} method, which splits a line into name and expression, determines whether it's a unit or a prefix, and parses the expression. Because all units are defined by others, base units need to be defined with a special expression "!"; \textbf{these units should be added to the database before parsing the file}.
@@ -154,10 +152,10 @@ Unit files contain data about units and prefixes. Each line contains the name o
Dimension files are similar, only for dimensions instead of units and prefixes.
\newpage
\section{Front-End Design}
-\label{sec:orgbb1d346}
+\label{sec:org261b06e}
The front-end of 7Units is based on an MVP model. There are two major frontend classes, the \textbf{View} and the \textbf{Presenter}.
\subsection{The View}
-\label{sec:orgae93ac0}
+\label{sec:org57b8a42}
The \texttt{View} is the part of the frontend code that directly interacts with the user. It handles input and output, but does not do any processing. Processing is handled by the presenter and the backend code.
The \texttt{View} is an interface, not a single class, so that I can easily create multiple views without having to rewrite any processing code. This allows me to easily prototype changes to the GUI without messing with existing code.
@@ -171,10 +169,10 @@ There are currently two implementations of the \texttt{View}:
\end{description}
Both of these \texttt{View} implementations implement \texttt{UnitConversionView} and \texttt{ExpressionConversionView}.
\subsection{The Presenter}
-\label{sec:org76432e4}
+\label{sec:orga668171}
The \texttt{Presenter} is an intermediary between the \texttt{View} and the backend code. It accepts the user's input and passes it to the backend, then accepts the backend's output and passes it to the frontend for user viewing. Its main functions do not have arguments or return values; instead it takes input from and provides output to the \texttt{View} via its public methods.
\subsubsection{Rules}
-\label{sec:org5218ce5}
+\label{sec:org81f6f8a}
The \texttt{Presenter} has a set of function-object rules that determine some of its behaviours. Each corresponds to a setting in the \texttt{View}, but they can be set to other values via the \texttt{Presenter}'s setters (although nonstandard rules cannot be saved and loaded):
\begin{description}
\item[{numberDisplayRule}] A function that determines how numbers are displayed. This controls the rounding rules.
@@ -184,7 +182,7 @@ The \texttt{Presenter} has a set of function-object rules that determine some of
These rules have been made this way to enable an incredible level of customization of these behaviours. Because any function object with the correct arguments and return type is accepted, these rules (especially the number display rule) can do much more than the default behaviours.
\subsection{Utility Classes}
-\label{sec:org0cfabd2}
+\label{sec:orga5b57ce}
The frontend has many miscellaneous utility classes. Many of them are package-private. Here is a list of them, with a brief description of what they do and where they are used:
\begin{description}
\item[{DefaultPrefixRepetitionRule}] An enum containing the available rules determining when you can repeat prefixes. Used by the \texttt{TabbedView} for selecting the rule and by the \texttt{Presenter} for loading it from a file.
@@ -197,38 +195,42 @@ The frontend has many miscellaneous utility classes. Many of them are package-p
\end{description}
\newpage
\section{Utility Classes}
-\label{sec:org59e5f0c}
+\label{sec:org3686e64}
7Units has a few general "utility" classes. They aren't directly related to units, but are used in the units system.
\subsection{ObjectProduct}
-\label{sec:org07580ff}
+\label{sec:org16bc96f}
An \texttt{ObjectProduct} represents a "product" of elements of some type. The units system uses them to represent coherent units as a product of base units, and dimensions as a product of base dimensions.
Internally, it is represented using a map mapping objects to their exponents in the product. For example, the unit "kg m\textsuperscript{2} / s\textsuperscript{2}" (i.e. a Joule) would be represented with a map like \texttt{[kg: 1, m: 2, s: -2]}.
\subsection{ExpressionParser}
-\label{sec:orgb075c07}
+\label{sec:org7296a14}
The \texttt{ExpressionParser} class is used to parse the unit, prefix and dimension expressions that are used throughout 7Units. An expression is something like "(2 m + 30 J / N) * 8 s)". Each instance represents a type of expression, containing a way to obtain values (such as numbers or units) from the text and operations that can be done on these values (such as addition, subtraction or multiplication). Each operation also has a priority, which controls the order of operations (i.e. multiplication gets a higher priority than addition).
\texttt{ExpressionParser} has a parameterized type \texttt{T}, which represents the type of the value used in the expression. The expression parser currently only supports one type of value per expression; in the expressions used by 7Units numbers are treated as a kind of unit or prefix. Operators are represented by internal types; the system distinguishes between unary operators (those that take a single value, like negation) and binary operators (those that take 2 values, like +, -, * or /).
+There is one exception to this rule - users are allowed to create "numeric operators" that take one argument of type \texttt{T} and one numeric argument. This is intended for exponentation, but could also be used for vector scaling.
+
Expressions are parsed in 2 steps:
\begin{enumerate}
\item Convert the expression to \href{https://en.wikipedia.org/wiki/Reverse\_Polish\_notation}{Reverse Polish Notation}, where operators come \textbf{after} the values they operate on, and brackets and the order of operations are not necessary. For example, "2 + 5" becomes "\texttt{2 5 +}", "(1 + 2) * 3" becomes "\texttt{1 2 + 3 *}" and the example expression earlier becomes "\texttt{2 m * 30 J * N / + 8 s * *}". This makes it simple to evaluate - early calculators used RPN for a good reason!
\item Evaluate the RPN expression. This can be done simply with a for loop and a stack. For each token in the expression, the progam does the following:
\begin{itemize}
\item if it is a number or unit, add it to the stack.
+(the dimension parser adds numbers to a separate stack for type safety, as numbers cannot be stored as a dimension type like they can with units.)
\item if it is a unary operator, take one value from the stack, apply the operator to it, and put the result into the stack.
\item if it is a binary operator, take two values from the stack, apply the operator to them, and put the result into the stack.
+\item if it is a numeric operator, take one value from the stack and one number from the numeric stack, apply to the operator to the two, and put the result in the regular stack.
\end{itemize}
After evaluating the last token, there should be one value left in the stack - the answer. If there isn't, the original expression was malformed.
\end{enumerate}
\subsection{Math Classes}
-\label{sec:org1e73788}
+\label{sec:orgfd8c723}
There are two simple math classes in 7Units:
\begin{description}
\item[{\texttt{UncertainDouble}}] Like a \texttt{double}, but with an uncertainty (e.g. \(2.0 \pm 0.4\)). The operations are like those of the regular Double, only they also calculate the uncertainty of the final value. They also have "exact" versions to help interoperation between \texttt{double} and \texttt{UncertainDouble}. It is used by the converter's Scientific Precision setting.
\item[{\texttt{DecimalComparison}}] A static utility class that contains a few alternate equals() methods for \texttt{double} and \texttt{UncertainDouble}. These methods allow a slight (configurable) difference between values to still be considered equal, to fight roundoff error.
\end{description}
\subsection{Collection Classes}
-\label{sec:org02e007a}
+\label{sec:org32d7d09}
The \texttt{ConditionalExistenceCollections} class contains wrapper implementations of \texttt{Collection}, \texttt{Iterator}, \texttt{Map} and \texttt{Set}. These implementations ignore elements that do not pass a certain condition - if an element fails the condition, \texttt{contains} will return false, the iterator will skip past it, it won't be counted in \texttt{size}, etc. even if it exists in the original collection. Effectively, any element of the original collection that fails the test does not exist.
\end{document}
diff --git a/docs/manual.org b/docs/manual.org
index 47302d3..3c6de1c 100644
--- a/docs/manual.org
+++ b/docs/manual.org
@@ -1,6 +1,6 @@
#+TITLE: 7Units User Manual
-#+SUBTITLE: For Version 0.4.0
-#+DATE: 2022 July 8
+#+SUBTITLE: For Version 0.5.0
+#+DATE: 2024 March 23
#+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry}
#+LaTeX: \newpage
@@ -73,11 +73,28 @@
** Miscellaneous Settings
- Convert One Way Only :: In the simple conversion tab, only imperial/customary units will be shown on the left, and only metric units[fn:1] will be shown on the right. Units listed in the exceptions file (~src/main/resources/metric_exceptions.txt~) will be shown on both sides. This is a way to reduce the number of options you must search through if you only convert one way. The expressions tab is unaffected.
- Show Duplicates in "Convert Units" :: If unchecked, any unit that has multiple names will only have one included in the Convert Units lists. The selected name will be the longest; if there are multiple longest names one is selected arbitrarily. You will still be able to use these alternate names in the expressions tab.
+** Configuration File
+The settings are saved in a configuration file. On Windows, this is located at \\
+~%USERPROFILE%/AppData/Local/SevenUnits/config.txt~. On other operating systems, this is located at ~$HOME/.config/SevenUnits/config.txt~. The directory containing the ~SevenUnits~ directory can be overridden with the environment variables ~$LOCALAPPDATA~ on Windows or ~$XDG_CONFIG_HOME~ elsewhere.
+
+Each line of this file contains a setting name, followed by an equal sign (not surrounded by spaces), then the setting value.
+
+The possible setting names are:
+- ~number_display_rule~ :: The rounding rule. This can be ~FIXED_DECIMALS~, ~FIXED_PRECISION~, or ~FIXED_UNCERTAINTY~. The first two must have a space then a number after them to set the number of decimal places or significant digits.
+- ~prefix_rule~ :: The prefix repetition rule; Can be either ~NO_REPETITION~, ~NO_RESTRICTION~, \\
+ or ~COMPLEX_REPETITION~.
+- ~one_way~ :: Whether One-Way Conversion is enabled; can be either ~true~ or ~false~.
+- ~include_duplicates~ :: Whether duplicate units should be shown; can be either ~true~ or ~false~.
+- ~search_prefix_rule~ :: The prefix search rule; can be ~NO_PREFIXES~, ~COMMON_PREFIXES~, \\
+ or ~ALL_METRIC_PREFIXES~.
+
+You can also use the special setting names ~custom_unit_file~, ~custom_dimension_file~ and ~custom_exception_file~ to add custom units, dimensions and metric exceptions to the system. These files use the same format as the standard files. These setting names can be used more than once to include multiple unit, dimension or exception files.
* Appendices
** Unit Expressions
A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence):
- Exponentiation (^); the exponent must be an integer. Both units and numbers can be raised to an exponent
- Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter").
+ You can also divide with ~|~ to create fractions. Using ~|~ instead of ~/~ gives the division a higher precedence than any other operator. For example, "2|5^2" evaluates to 4/25, not 2/25.
- Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds.
Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \deg C
diff --git a/docs/manual.pdf b/docs/manual.pdf
index 76ec078..5fcc115 100644
--- a/docs/manual.pdf
+++ b/docs/manual.pdf
Binary files differ
diff --git a/docs/manual.tex b/docs/manual.tex
index e82dac5..e16198f 100644
--- a/docs/manual.tex
+++ b/docs/manual.tex
@@ -1,29 +1,27 @@
-% Created 2022-07-17 Sun 16:22
+% Created 2024-03-24 Sun 13:16
% Intended LaTeX compiler: pdflatex
\documentclass[11pt]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{graphicx}
-\usepackage{grffile}
\usepackage{longtable}
\usepackage{wrapfig}
\usepackage{rotating}
\usepackage[normalem]{ulem}
\usepackage{amsmath}
-\usepackage{textcomp}
\usepackage{amssymb}
\usepackage{capt-of}
\usepackage{hyperref}
\usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry}
-\date{2022 July 8}
+\date{2024 March 23}
\title{7Units User Manual\\\medskip
-\large For Version 0.4.0}
+\large For Version 0.5.0}
\hypersetup{
pdfauthor={},
pdftitle={7Units User Manual},
pdfkeywords={},
pdfsubject={},
- pdfcreator={Emacs 27.1 (Org mode 9.4.6)},
+ pdfcreator={Emacs 29.2 (Org mode 9.6.15)},
pdflang={English}}
\begin{document}
@@ -32,21 +30,21 @@
\newpage
\section{Introduction and Purpose}
-\label{sec:org24a029b}
+\label{sec:orgc09fcc7}
7Units is a program that can be used to convert units. This document outlines how to use the program.
\section{System Requirements}
-\label{sec:orgab28d01}
+\label{sec:orga902335}
\begin{itemize}
-\item Works on all major operating systems \\
+\item Works on all major operating systems \\[0pt]
\textbf{NOTE:} All screenshots in this document were taken on Windows 10. If you use a different operating system, the program will probably look different than what is shown.
\item Java version 11+ required
\end{itemize}
\newpage
\section{How to Use 7Units}
-\label{sec:org23427ab}
+\label{sec:orgdec078f}
\subsection{Simple Unit Conversion}
-\label{sec:org91a1ab6}
+\label{sec:org785ebcb}
\begin{enumerate}
\item Select the "Convert Units" tab if it is not already selected. You should see a screen like in figure \ref{main-interface-dimension}:
\begin{figure}[htbp]
@@ -71,7 +69,7 @@
\end{figure}
\end{enumerate}
\subsection{Complex Unit Conversion}
-\label{sec:orgf8fd5b1}
+\label{sec:org75a0192}
\begin{enumerate}
\item Select the "Convert Unit Expressions" if it is not already selected. You should see a screen like in figure \ref{main-interface-expression}:
\begin{figure}[htbp]
@@ -79,7 +77,7 @@
\includegraphics[height=250px]{../screenshots/main-interface-expression-converter.png}
\caption{\label{main-interface-expression}Taken in version 0.3.0}
\end{figure}
-\item Enter a \hyperref[sec:org7ac3fe5]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}".
+\item Enter a \hyperref[sec:org3724d84]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}".
\item Enter a unit name (or another unit expression) in the To box.
\item Press the Convert button. This will calculate the value of the first expression, and convert it to a multiple of the second unit (or expression).
\begin{figure}[htbp]
@@ -89,7 +87,7 @@
\end{figure}
\end{enumerate}
\section{7Units Settings}
-\label{sec:org72dc17b}
+\label{sec:orgae2806f}
All settings can be accessed in the tab with the gear icon.
\begin{figure}[htbp]
\centering
@@ -97,7 +95,7 @@ All settings can be accessed in the tab with the gear icon.
\caption{The settings menu, as of version 0.4.0}
\end{figure}
\subsection{Rounding Settings}
-\label{sec:org690a0a5}
+\label{sec:org6d3e49c}
These settings control how the output of a unit conversion is rounded.
\begin{description}
\item[{Fixed Precision}] Round to a fixed number of \href{https://en.wikipedia.org/wiki/Significant\_figures}{significant digits}. The number of significant digits is controlled by the precision slider below.
@@ -105,7 +103,7 @@ These settings control how the output of a unit conversion is rounded.
\item[{Scientific Precision}] Intelligent rounding which uses the precision of the input value(s) to determine the output precision. Not affected by the precision slider.
\end{description}
\subsection{Prefix Repetition Settings}
-\label{sec:orgef19465}
+\label{sec:org9aa98f8}
These settings control when you are allowed to repeat unit prefixes (e.g. kilokilometre)
\begin{description}
\item[{No Repetition}] Units may only have one prefix.
@@ -120,7 +118,7 @@ These settings control when you are allowed to repeat unit prefixes (e.g. kiloki
\end{itemize}
\end{description}
\subsection{Search Settings}
-\label{sec:org038add7}
+\label{sec:org2745ba0}
These settings control which prefixes are shown in the "Convert Units" tab. Only coherent SI units (e.g. metre, second, newton, joule) will get prefixes. Some prefixed units are created in the unitfile, and will stay regardless of this setting (though they can be removed from the unitfile).
\begin{description}
\item[{Never Include Prefixed Units}] Prefixed units will only be shown if they are explicitly added to the unitfile.
@@ -128,24 +126,44 @@ These settings control which prefixes are shown in the "Convert Units" tab. Onl
\item[{Include All Single Prefixes}] Every coherent unit will have every prefixed version of it included in the list.
\end{description}
\subsection{Miscellaneous Settings}
-\label{sec:orgbd23cc6}
+\label{sec:orgeabb2df}
\begin{description}
\item[{Convert One Way Only}] In the simple conversion tab, only imperial/customary units will be shown on the left, and only metric units\footnote{7Units's definition of "metric" is stricter than the SI, but all of the common units that are commonly considered metric but not included in 7Units's definition are included in the exceptions file.} will be shown on the right. Units listed in the exceptions file (\texttt{src/main/resources/metric\_exceptions.txt}) will be shown on both sides. This is a way to reduce the number of options you must search through if you only convert one way. The expressions tab is unaffected.
\item[{Show Duplicates in "Convert Units"}] If unchecked, any unit that has multiple names will only have one included in the Convert Units lists. The selected name will be the longest; if there are multiple longest names one is selected arbitrarily. You will still be able to use these alternate names in the expressions tab.
\end{description}
+\subsection{Configuration File}
+\label{sec:org4cc2874}
+The settings are saved in a configuration file. On Windows, this is located at \\[0pt]
+\texttt{\%USERPROFILE\%/AppData/Local/SevenUnits/config.txt}. On other operating systems, this is located at \texttt{\$HOME/.config/SevenUnits/config.txt}. The directory containing the \texttt{SevenUnits} directory can be overridden with the environment variables \texttt{\$LOCALAPPDATA} on Windows or \texttt{\$XDG\_CONFIG\_HOME} elsewhere.
+
+Each line of this file contains a setting name, followed by an equal sign (not surrounded by spaces), then the setting value.
+
+The possible setting names are:
+\begin{description}
+\item[{\texttt{number\_display\_rule}}] The rounding rule. This can be \texttt{FIXED\_DECIMALS}, \texttt{FIXED\_PRECISION}, or \texttt{FIXED\_UNCERTAINTY}. The first two must have a space then a number after them to set the number of decimal places or significant digits.
+\item[{\texttt{prefix\_rule}}] The prefix repetition rule; Can be either \texttt{NO\_REPETITION}, \texttt{NO\_RESTRICTION}, \\[0pt]
+or \texttt{COMPLEX\_REPETITION}.
+\item[{\texttt{one\_way}}] Whether One-Way Conversion is enabled; can be either \texttt{true} or \texttt{false}.
+\item[{\texttt{include\_duplicates}}] Whether duplicate units should be shown; can be either \texttt{true} or \texttt{false}.
+\item[{\texttt{search\_prefix\_rule}}] The prefix search rule; can be \texttt{NO\_PREFIXES}, \texttt{COMMON\_PREFIXES}, \\[0pt]
+or \texttt{ALL\_METRIC\_PREFIXES}.
+\end{description}
+
+You can also use the special setting names \texttt{custom\_unit\_file}, \texttt{custom\_dimension\_file} and \texttt{custom\_exception\_file} to add custom units, dimensions and metric exceptions to the system. These files use the same format as the standard files. These setting names can be used more than once to include multiple unit, dimension or exception files.
\section{Appendices}
-\label{sec:org4ceffe9}
+\label{sec:org60385a7}
\subsection{Unit Expressions}
-\label{sec:org7ac3fe5}
+\label{sec:org3724d84}
A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence):
\begin{itemize}
\item Exponentiation (\^{}); the exponent must be an integer. Both units and numbers can be raised to an exponent
\item Multiplication (*) and division (/). Multiplication can also be done with a space (so "15 meter" is the same thing as "15 * meter").
+You can also divide with \texttt{|} to create fractions. Using \texttt{|} instead of \texttt{/} gives the division a higher precedence than any other operator. For example, "2|5\textsuperscript{2}" evaluates to 4/25, not 2/25.
\item Addition (+) and subtraction (-). They can only be done between units of the same dimension (measuring the same thing). So you can add metres, inches and feet together, and you can add joules and calories together, but you can't add metres to seconds, or feet to calories, or watts to pounds.
\end{itemize}
Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \textdegree{} C
\subsection{Other Expressions}
-\label{sec:orga66137e}
+\label{sec:orgc72a672}
There are also a simplified version of expressions for prefixes and dimensions. Only multiplication, division and exponentation are supported. Currently, exponentation is not supported for dimensions, but that may be fixed in the future.
\end{document}
diff --git a/docs/roadmap.org b/docs/roadmap.org
new file mode 100644
index 0000000..5a3888f
--- /dev/null
+++ b/docs/roadmap.org
@@ -0,0 +1,22 @@
+* Version 1.0.0 Roadmap
+Here is a list of the unfinished requirements for version 1.0.0. When everything here is met, I intend to release version 1.0.0 and consider 7Units complete (for the most part).
+
+These requirements are subject to change. I intend to finish version 1.0.0 by [2025-04-27 Sun].
+
+Feature Requirements:
+- 7Units should be able to parse unit files from [[https://www.gnu.org/software/units/][GNU Units]], the program that inspired it.
+ (It should not be required to handle features that aren't in 7Units; those definitions should be ignored with a warning)
+- 7Units's expression converter should support most or all of the conversion features supported by GNU Units:
+ - Converting to sums of units (it should also be possible to do this in the unit converter with preset combinations)
+ - Non-integer exponents
+ - (/Optional/) Inverse nonlinear conversion with the tilde prefix
+ - (/Optional/) Nonlinear units should be specifiable in unit files.
+ - /Any other feature not listed should be considered optional./
+- (/Optional/) It should be possible to add, edit and remove units and prefixes from the GUI unit and prefix viewers.
+
+Documentation/Testing Requirements:
+- 7Units should be fully documented.
+- 7Units should have automated testing with a code coverage of at least 2/3 (ideally at least 5/6).
+
+Other Requirements
+- The public API of 7Units should be finalized and well-designed.
diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java
index da66b4c..9c23c49 100644
--- a/src/main/java/sevenUnits/ProgramInfo.java
+++ b/src/main/java/sevenUnits/ProgramInfo.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021-2023 Adrien Hopkins
+ * Copyright (C) 2021-2024 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
@@ -25,15 +25,15 @@ import sevenUnits.utils.SemanticVersionNumber;
* @since 2021-06-28
*/
public final class ProgramInfo {
-
- /** The version number (0.4.1) */
+
+ /** The version number (0.5.0) */
public static final SemanticVersionNumber VERSION = SemanticVersionNumber
- .stableVersion(0, 4, 1);
-
+ .stableVersion(0, 5, 0);
+
private ProgramInfo() {
// this class is only for static variables, you shouldn't be able to
// construct an instance
throw new AssertionError();
}
-
+
}
diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java
index 820d48c..3f1f75f 100644
--- a/src/main/java/sevenUnits/unit/BaseDimension.java
+++ b/src/main/java/sevenUnits/unit/BaseDimension.java
@@ -39,7 +39,7 @@ public final class BaseDimension implements Nameable {
public static BaseDimension valueOf(final String name, final String symbol) {
return new BaseDimension(name, symbol);
}
-
+
/**
* The name of the dimension.
*/
@@ -49,7 +49,7 @@ public final class BaseDimension implements Nameable {
* or two characters.
*/
private final String symbol;
-
+
/**
* Creates the {@code BaseDimension}.
*
@@ -62,7 +62,7 @@ public final class BaseDimension implements Nameable {
this.name = Objects.requireNonNull(name, "name must not be null.");
this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
}
-
+
/**
* @since v0.4.0
*/
@@ -70,7 +70,7 @@ public final class BaseDimension implements Nameable {
public NameSymbol getNameSymbol() {
return NameSymbol.of(this.name, this.symbol);
}
-
+
@Override
public String toString() {
return String.format("%s (%s)", this.name, this.symbol);
diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java
index dba7f52..fe85a7b 100644
--- a/src/main/java/sevenUnits/unit/BaseUnit.java
+++ b/src/main/java/sevenUnits/unit/BaseUnit.java
@@ -46,7 +46,7 @@ public final class BaseUnit extends Unit {
final String name, final String symbol) {
return new BaseUnit(dimension, name, symbol, new HashSet<>());
}
-
+
/**
* Gets a base unit from the dimension it measures, its name and its symbol.
*
@@ -60,12 +60,12 @@ public final class BaseUnit extends Unit {
final String name, final String symbol, final Set<String> otherNames) {
return new BaseUnit(dimension, name, symbol, otherNames);
}
-
+
/**
* The dimension measured by this base unit.
*/
private final BaseDimension dimension;
-
+
/**
* Creates the {@code BaseUnit}.
*
@@ -81,7 +81,7 @@ public final class BaseUnit extends Unit {
this.dimension = Objects.requireNonNull(dimension,
"dimension must not be null.");
}
-
+
/**
* Returns a {@code LinearUnit} with this unit as a base and a conversion
* factor of 1. This operation must be done in order to allow units to be
@@ -93,17 +93,17 @@ public final class BaseUnit extends Unit {
public LinearUnit asLinearUnit() {
return LinearUnit.valueOf(this.getBase(), 1);
}
-
+
@Override
protected double convertFromBase(final double value) {
return value;
}
-
+
@Override
protected double convertToBase(final double value) {
return value;
}
-
+
/**
* @return dimension
* @since 2019-10-16
@@ -111,7 +111,7 @@ public final class BaseUnit extends Unit {
public final BaseDimension getBaseDimension() {
return this.dimension;
}
-
+
@Override
public String toString() {
return this.getPrimaryName().orElse("Unnamed unit")
@@ -119,7 +119,7 @@ public final class BaseUnit extends Unit {
? String.format(" (%s)", this.getSymbol().get())
: "");
}
-
+
@Override
public BaseUnit withName(final NameSymbol ns) {
Objects.requireNonNull(ns, "ns must not be null.");
diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java
index 0ecba6d..69a3c05 100644
--- a/src/main/java/sevenUnits/unit/BritishImperial.java
+++ b/src/main/java/sevenUnits/unit/BritishImperial.java
@@ -39,7 +39,7 @@ public final class BritishImperial {
public static final LinearUnit ROOD = Length.ROD.times(Length.FURLONG);
public static final LinearUnit ACRE = Length.FURLONG.times(Length.CHAIN);
}
-
+
/**
* Imperial units that measure length
*
@@ -59,15 +59,15 @@ public final class BritishImperial {
public static final LinearUnit FURLONG = CHAIN.times(10);
public static final LinearUnit MILE = FURLONG.times(8);
public static final LinearUnit LEAGUE = MILE.times(3);
-
+
public static final LinearUnit NAUTICAL_MILE = Metric.METRE.times(1852);
public static final LinearUnit CABLE = NAUTICAL_MILE.dividedBy(10);
public static final LinearUnit FATHOM = CABLE.dividedBy(100);
-
+
public static final LinearUnit ROD = YARD.times(5.5);
public static final LinearUnit LINK = ROD.dividedBy(25);
}
-
+
/**
* British Imperial units that measure mass.
*
@@ -85,7 +85,7 @@ public final class BritishImperial {
public static final LinearUnit LONG_TON = HUNDREDWEIGHT.times(20);
public static final LinearUnit SLUG = Metric.KILOGRAM.times(14.59390294);
}
-
+
/**
* British Imperial units that measure volume
*
@@ -101,23 +101,23 @@ public final class BritishImperial {
public static final LinearUnit GALLON = QUART.times(4);
public static final LinearUnit PECK = GALLON.times(2);
public static final LinearUnit BUSHEL = PECK.times(4);
-
+
public static final LinearUnit CUBIC_INCH = Length.INCH.toExponent(3);
public static final LinearUnit CUBIC_FOOT = Length.FOOT.toExponent(3);
public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3);
public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT);
}
-
+
public static final LinearUnit OUNCE_FORCE = Mass.OUNCE
.times(Metric.Constants.EARTH_GRAVITY);
public static final LinearUnit POUND_FORCE = Mass.POUND
.times(Metric.Constants.EARTH_GRAVITY);
-
+
public static final LinearUnit BRITISH_THERMAL_UNIT = Metric.JOULE
.times(1055.06);
public static final LinearUnit CALORIE = Metric.JOULE.times(4.184);
public static final LinearUnit KILOCALORIE = Metric.JOULE.times(4184);
-
+
public static final Unit FAHRENHEIT = Unit
.fromConversionFunctions(Metric.KELVIN.getBase(),
tempK -> tempK * 1.8 - 459.67, tempF -> (tempF + 459.67) / 1.8)
diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java
index 720b0af..6de446f 100644
--- a/src/main/java/sevenUnits/unit/FunctionalUnit.java
+++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java
@@ -30,14 +30,16 @@ import sevenUnits.utils.ObjectProduct;
*/
final class FunctionalUnit extends Unit {
/**
- * A function that accepts a value expressed in the unit's base and returns that value expressed in this unit.
+ * A function that accepts a value expressed in the unit's base and returns
+ * that value expressed in this unit.
*
* @since 2019-05-22
*/
private final DoubleUnaryOperator converterFrom;
/**
- * A function that accepts a value expressed in the unit and returns that value expressed in the unit's base.
+ * A function that accepts a value expressed in the unit and returns that
+ * value expressed in the unit's base.
*
* @since 2019-05-22
*/
@@ -46,45 +48,43 @@ final class FunctionalUnit extends Unit {
/**
* Creates the {@code FunctionalUnit}.
*
- * @param base
- * unit's base
- * @param converterFrom
- * function that accepts a value expressed in the unit's base and returns that value expressed in this
- * unit.
- * @param converterTo
- * function that accepts a value expressed in the unit and returns that value expressed in the unit's
- * base.
- * @throws NullPointerException
- * if any argument is null
+ * @param base unit's base
+ * @param converterFrom function that accepts a value expressed in the unit's
+ * base and returns that value expressed in this unit.
+ * @param converterTo function that accepts a value expressed in the unit
+ * and returns that value expressed in the unit's base.
+ * @throws NullPointerException if any argument is null
* @since 2019-05-22
*/
- public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom,
+ public FunctionalUnit(final ObjectProduct<BaseUnit> base,
+ final DoubleUnaryOperator converterFrom,
final DoubleUnaryOperator converterTo) {
super(base, NameSymbol.EMPTY);
- this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null.");
- this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null.");
+ this.converterFrom = Objects.requireNonNull(converterFrom,
+ "converterFrom must not be null.");
+ this.converterTo = Objects.requireNonNull(converterTo,
+ "converterTo must not be null.");
}
/**
* Creates the {@code FunctionalUnit}.
*
- * @param base
- * unit's base
- * @param converterFrom
- * function that accepts a value expressed in the unit's base and returns that value expressed in this
- * unit.
- * @param converterTo
- * function that accepts a value expressed in the unit and returns that value expressed in the unit's
- * base.
- * @throws NullPointerException
- * if any argument is null
+ * @param base unit's base
+ * @param converterFrom function that accepts a value expressed in the unit's
+ * base and returns that value expressed in this unit.
+ * @param converterTo function that accepts a value expressed in the unit
+ * and returns that value expressed in the unit's base.
+ * @throws NullPointerException if any argument is null
* @since 2019-05-22
*/
- public FunctionalUnit(final ObjectProduct<BaseUnit> base, final DoubleUnaryOperator converterFrom,
+ public FunctionalUnit(final ObjectProduct<BaseUnit> base,
+ final DoubleUnaryOperator converterFrom,
final DoubleUnaryOperator converterTo, final NameSymbol ns) {
super(base, ns);
- this.converterFrom = Objects.requireNonNull(converterFrom, "converterFrom must not be null.");
- this.converterTo = Objects.requireNonNull(converterTo, "converterTo must not be null.");
+ this.converterFrom = Objects.requireNonNull(converterFrom,
+ "converterFrom must not be null.");
+ this.converterTo = Objects.requireNonNull(converterTo,
+ "converterTo must not be null.");
}
/**
diff --git a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java
index d6046c0..e9b4d1f 100644
--- a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java
+++ b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java
@@ -35,13 +35,13 @@ final class FunctionalUnitlike<V> extends Unitlike<V> {
* @since 2020-09-07
*/
private final DoubleFunction<V> converterFrom;
-
+
/**
* A function that accepts a value in the unitlike form and returns a value
* in the unitlike form's base.
*/
private final ToDoubleFunction<V> converterTo;
-
+
/**
* Creates the {@code FunctionalUnitlike}.
*
@@ -59,15 +59,15 @@ final class FunctionalUnitlike<V> extends Unitlike<V> {
this.converterFrom = converterFrom;
this.converterTo = converterTo;
}
-
+
@Override
protected V convertFromBase(double value) {
return this.converterFrom.apply(value);
}
-
+
@Override
protected double convertToBase(V value) {
return this.converterTo.applyAsDouble(value);
}
-
+
}
diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java
index 103b7f6..6489229 100644
--- a/src/main/java/sevenUnits/unit/LinearUnit.java
+++ b/src/main/java/sevenUnits/unit/LinearUnit.java
@@ -46,7 +46,7 @@ public final class LinearUnit extends Unit {
Objects.requireNonNull(unit, "unit must not be null.").getBase(),
unit.convertToBase(value), NameSymbol.EMPTY);
}
-
+
/**
* Gets a {@code LinearUnit} from a unit and a value. For example, converts
* '59 °F' to a linear unit with the value of '288.15 K'
@@ -64,7 +64,7 @@ public final class LinearUnit extends Unit {
Objects.requireNonNull(unit, "unit must not be null.").getBase(),
unit.convertToBase(value), ns);
}
-
+
/**
* @return the base unit associated with {@code unit}, as a
* {@code LinearUnit}.
@@ -73,7 +73,7 @@ public final class LinearUnit extends Unit {
public static LinearUnit getBase(final Unit unit) {
return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY);
}
-
+
/**
* @return the base unit associated with {@code unitlike}, as a
* {@code LinearUnit}.
@@ -82,7 +82,7 @@ public final class LinearUnit extends Unit {
public static LinearUnit getBase(final Unitlike<?> unit) {
return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY);
}
-
+
/**
* Gets a {@code LinearUnit} from a unit base and a conversion factor. In
* other words, gets the product of {@code unitBase} and
@@ -98,7 +98,7 @@ public final class LinearUnit extends Unit {
final double conversionFactor) {
return new LinearUnit(unitBase, conversionFactor, NameSymbol.EMPTY);
}
-
+
/**
* Gets a {@code LinearUnit} from a unit base and a conversion factor. In
* other words, gets the product of {@code unitBase} and
@@ -115,7 +115,7 @@ public final class LinearUnit extends Unit {
final double conversionFactor, final NameSymbol ns) {
return new LinearUnit(unitBase, conversionFactor, ns);
}
-
+
/**
* The value of this unit as represented in its base form. Mathematically,
*
@@ -126,7 +126,7 @@ public final class LinearUnit extends Unit {
* @since 2019-10-16
*/
private final double conversionFactor;
-
+
/**
* Creates the {@code LinearUnit}.
*
@@ -139,7 +139,7 @@ public final class LinearUnit extends Unit {
super(unitBase, ns);
this.conversionFactor = conversionFactor;
}
-
+
/**
* {@inheritDoc}
*
@@ -149,7 +149,7 @@ public final class LinearUnit extends Unit {
protected double convertFromBase(final double value) {
return value / this.getConversionFactor();
}
-
+
/**
* Converts an {@code UncertainDouble} value expressed in this unit to an
* {@code UncertainValue} value expressed in {@code other}.
@@ -172,9 +172,9 @@ public final class LinearUnit extends Unit {
else
throw new IllegalArgumentException(
String.format("Cannot convert from %s to %s.", this, other));
-
+
}
-
+
/**
* {@inheritDoc}
*
@@ -184,7 +184,7 @@ public final class LinearUnit extends Unit {
protected double convertToBase(final double value) {
return value * this.getConversionFactor();
}
-
+
/**
* Converts an {@code UncertainDouble} to the base unit.
*
@@ -193,7 +193,7 @@ public final class LinearUnit extends Unit {
UncertainDouble convertToBase(final UncertainDouble value) {
return value.timesExact(this.getConversionFactor());
}
-
+
/**
* Divides this unit by a scalar.
*
@@ -205,7 +205,7 @@ public final class LinearUnit extends Unit {
public LinearUnit dividedBy(final double divisor) {
return valueOf(this.getBase(), this.getConversionFactor() / divisor);
}
-
+
/**
* Returns the quotient of this unit and another.
*
@@ -217,14 +217,14 @@ public final class LinearUnit extends Unit {
*/
public LinearUnit dividedBy(final LinearUnit divisor) {
Objects.requireNonNull(divisor, "other must not be null");
-
+
// divide the units
final ObjectProduct<BaseUnit> base = this.getBase()
.dividedBy(divisor.getBase());
return valueOf(base,
this.getConversionFactor() / divisor.getConversionFactor());
}
-
+
/**
* {@inheritDoc}
*
@@ -239,7 +239,7 @@ public final class LinearUnit extends Unit {
&& DecimalComparison.equals(this.getConversionFactor(),
other.getConversionFactor());
}
-
+
/**
* @return conversion factor
* @since 2019-10-16
@@ -247,7 +247,7 @@ public final class LinearUnit extends Unit {
public double getConversionFactor() {
return this.conversionFactor;
}
-
+
/**
* {@inheritDoc}
*
@@ -258,7 +258,7 @@ public final class LinearUnit extends Unit {
return 31 * this.getBase().hashCode()
+ DecimalComparison.hash(this.getConversionFactor());
}
-
+
/**
* @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there
* is a {@code BaseUnit b} where
@@ -268,7 +268,7 @@ public final class LinearUnit extends Unit {
public boolean isBase() {
return this.isCoherent() && this.getBase().isSingleObject();
}
-
+
/**
* @return whether this unit is coherent (i.e. has conversion factor 1)
* @since 2019-10-16
@@ -276,7 +276,7 @@ public final class LinearUnit extends Unit {
public boolean isCoherent() {
return this.getConversionFactor() == 1;
}
-
+
/**
* Returns the difference of this unit and another.
* <p>
@@ -296,18 +296,18 @@ public final class LinearUnit extends Unit {
*/
public LinearUnit minus(final LinearUnit subtrahend) {
Objects.requireNonNull(subtrahend, "addend must not be null.");
-
+
// reject subtrahends that cannot be added to this unit
if (!this.getBase().equals(subtrahend.getBase()))
throw new IllegalArgumentException(String.format(
"Incompatible units for subtraction \"%s\" and \"%s\".", this,
subtrahend));
-
+
// subtract the units
return valueOf(this.getBase(),
this.getConversionFactor() - subtrahend.getConversionFactor());
}
-
+
/**
* Returns the sum of this unit and another.
* <p>
@@ -327,18 +327,18 @@ public final class LinearUnit extends Unit {
*/
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.getBase().equals(addend.getBase()))
throw new IllegalArgumentException(String.format(
"Incompatible units for addition \"%s\" and \"%s\".", this,
addend));
-
+
// add the units
return valueOf(this.getBase(),
this.getConversionFactor() + addend.getConversionFactor());
}
-
+
/**
* Multiplies this unit by a scalar.
*
@@ -350,7 +350,7 @@ public final class LinearUnit extends Unit {
public LinearUnit times(final double multiplier) {
return valueOf(this.getBase(), this.getConversionFactor() * multiplier);
}
-
+
/**
* Returns the product of this unit and another.
*
@@ -362,21 +362,21 @@ public final class LinearUnit extends Unit {
*/
public LinearUnit times(final LinearUnit multiplier) {
Objects.requireNonNull(multiplier, "other must not be null");
-
+
// multiply the units
final ObjectProduct<BaseUnit> base = this.getBase()
.times(multiplier.getBase());
return valueOf(base,
this.getConversionFactor() * multiplier.getConversionFactor());
}
-
+
@Override
public String toDefinitionString() {
return Double.toString(this.conversionFactor)
+ (this.getBase().equals(ObjectProduct.empty()) ? ""
: " " + this.getBase().toString(BaseUnit::getShortName));
}
-
+
/**
* Returns this unit but to an exponent.
*
@@ -389,12 +389,12 @@ public final class LinearUnit extends Unit {
return valueOf(this.getBase().toExponent(exponent),
Math.pow(this.conversionFactor, exponent));
}
-
+
@Override
public LinearUnit withName(final NameSymbol ns) {
return valueOf(this.getBase(), this.getConversionFactor(), ns);
}
-
+
/**
* Returns the result of applying {@code prefix} to this unit.
* <p>
@@ -413,7 +413,7 @@ public final class LinearUnit extends Unit {
*/
public LinearUnit withPrefix(final UnitPrefix prefix) {
final LinearUnit unit = this.times(prefix.getMultiplier());
-
+
// create new name and symbol, if possible
final String name;
if (this.getPrimaryName().isPresent()
@@ -422,14 +422,14 @@ public final class LinearUnit extends Unit {
} else {
name = null;
}
-
+
final String symbol;
if (this.getSymbol().isPresent() && prefix.getSymbol().isPresent()) {
symbol = prefix.getSymbol().get() + this.getSymbol().get();
} else {
symbol = null;
}
-
+
return unit.withName(NameSymbol.ofNullable(name, symbol));
}
}
diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java
index f91d30b..3a9428b 100644
--- a/src/main/java/sevenUnits/unit/LinearUnitValue.java
+++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java
@@ -34,7 +34,7 @@ import sevenUnits.utils.UncertainDouble;
*/
public final class LinearUnitValue {
public static final LinearUnitValue ONE = getExact(Metric.ONE, 1);
-
+
/**
* Gets an exact {@code LinearUnitValue}
*
@@ -49,7 +49,7 @@ public final class LinearUnitValue {
Objects.requireNonNull(unit, "unit must not be null"),
UncertainDouble.of(value, 0));
}
-
+
/**
* Gets an uncertain {@code LinearUnitValue}
*
@@ -65,11 +65,11 @@ public final class LinearUnitValue {
Objects.requireNonNull(unit, "unit must not be null"),
Objects.requireNonNull(value, "value may not be null"));
}
-
+
private final LinearUnit unit;
-
+
private final UncertainDouble value;
-
+
/**
* @param unit unit to express as
* @param value value to express
@@ -79,7 +79,7 @@ public final class LinearUnitValue {
this.unit = unit;
this.value = value;
}
-
+
/**
* @return this value as a {@code UnitValue}. All uncertainty information is
* removed from the returned value.
@@ -88,7 +88,7 @@ public final class LinearUnitValue {
public final UnitValue asUnitValue() {
return UnitValue.of(this.unit, this.value.value());
}
-
+
/**
* @param other a {@code LinearUnit}
* @return true iff this value can be represented with {@code other}.
@@ -97,7 +97,7 @@ public final class LinearUnitValue {
public final boolean canConvertTo(final LinearUnit other) {
return this.unit.canConvertTo(other);
}
-
+
/**
* Returns a LinearUnitValue that represents the same value expressed in a
* different unit
@@ -109,7 +109,7 @@ public final class LinearUnitValue {
public final LinearUnitValue convertTo(final LinearUnit other) {
return LinearUnitValue.of(other, this.unit.convertTo(other, this.value));
}
-
+
/**
* Divides this value by a scalar
*
@@ -120,7 +120,7 @@ public final class LinearUnitValue {
public LinearUnitValue dividedBy(final double divisor) {
return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor));
}
-
+
/**
* Divides this value by another value
*
@@ -132,7 +132,7 @@ public final class LinearUnitValue {
return LinearUnitValue.of(this.unit.dividedBy(divisor.unit),
this.value.dividedBy(divisor.value));
}
-
+
/**
* Returns true if this and obj represent the same value, regardless of
* whether or not they are expressed in the same unit. So (1000 m).equals(1
@@ -150,7 +150,7 @@ public final class LinearUnitValue {
&& this.unit.convertToBase(this.value)
.equals(other.unit.convertToBase(other.value));
}
-
+
/**
* Returns true if this and obj represent the same value, regardless of
* whether or not they are expressed in the same unit. So (1000 m).equals(1
@@ -171,7 +171,7 @@ public final class LinearUnitValue {
&& DecimalComparison.equals(this.unit.convertToBase(this.value),
other.unit.convertToBase(other.value));
}
-
+
/**
* @param other another {@code LinearUnitValue}
* @return true iff this and other are within each other's uncertainty range
@@ -185,10 +185,10 @@ public final class LinearUnitValue {
final LinearUnit base = LinearUnit.valueOf(this.unit.getBase(), 1);
final LinearUnitValue thisBase = this.convertTo(base);
final LinearUnitValue otherBase = other.convertTo(base);
-
+
return thisBase.value.equivalent(otherBase.value);
}
-
+
/**
* @return the unit
* @since 2020-09-29
@@ -196,7 +196,7 @@ public final class LinearUnitValue {
public final LinearUnit getUnit() {
return this.unit;
}
-
+
/**
* @return the value
* @since 2020-09-29
@@ -204,7 +204,7 @@ public final class LinearUnitValue {
public final UncertainDouble getValue() {
return this.value;
}
-
+
/**
* @return the exact value
* @since 2020-09-07
@@ -212,13 +212,13 @@ public final class LinearUnitValue {
public final double getValueExact() {
return this.value.value();
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.unit.getBase(),
this.unit.convertToBase(this.getValue()));
}
-
+
/**
* Returns the difference of this value and another, expressed in this
* value's unit
@@ -231,17 +231,17 @@ public final class LinearUnitValue {
*/
public LinearUnitValue minus(final LinearUnitValue subtrahend) {
Objects.requireNonNull(subtrahend, "subtrahend may not be null");
-
+
if (!this.canConvertTo(subtrahend.unit))
throw new IllegalArgumentException(String.format(
"Incompatible units for subtraction \"%s\" and \"%s\".",
this.unit, subtrahend.unit));
-
+
final LinearUnitValue otherConverted = subtrahend.convertTo(this.unit);
return LinearUnitValue.of(this.unit,
this.value.minus(otherConverted.value));
}
-
+
/**
* Returns the sum of this value and another, expressed in this value's unit
*
@@ -253,17 +253,17 @@ public final class LinearUnitValue {
*/
public LinearUnitValue plus(final LinearUnitValue addend) {
Objects.requireNonNull(addend, "addend may not be null");
-
+
if (!this.canConvertTo(addend.unit))
throw new IllegalArgumentException(String.format(
"Incompatible units for addition \"%s\" and \"%s\".", this.unit,
addend.unit));
-
+
final LinearUnitValue otherConverted = addend.convertTo(this.unit);
return LinearUnitValue.of(this.unit,
this.value.plus(otherConverted.value));
}
-
+
/**
* Multiplies this value by a scalar
*
@@ -274,7 +274,7 @@ public final class LinearUnitValue {
public LinearUnitValue times(final double multiplier) {
return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier));
}
-
+
/**
* Multiplies this value by another value
*
@@ -286,7 +286,7 @@ public final class LinearUnitValue {
return LinearUnitValue.of(this.unit.times(multiplier.unit),
this.value.times(multiplier.value));
}
-
+
/**
* Raises a value to an exponent
*
@@ -298,18 +298,18 @@ public final class LinearUnitValue {
return LinearUnitValue.of(this.unit.toExponent(exponent),
this.value.toExponentExact(exponent));
}
-
+
@Override
public String toString() {
return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN);
}
-
+
/**
* Returns a string representing the object. <br>
* If the attached unit has a name or symbol, the string looks like "12 km".
* Otherwise, it looks like "13 unnamed unit (= 2 m/s)".
* <p>
- * If showUncertainty is true, strings like "35 ± 8" are shown instead of
+ * If showUncertainty is true, strings like "35 � 8" are shown instead of
* single numbers.
* <p>
* Non-exact values are rounded intelligently based on their uncertainty.
@@ -321,9 +321,9 @@ public final class LinearUnitValue {
final Optional<String> primaryName = this.unit.getPrimaryName();
final Optional<String> symbol = this.unit.getSymbol();
final String chosenName = symbol.orElse(primaryName.orElse(null));
-
+
final UncertainDouble baseValue = this.unit.convertToBase(this.value);
-
+
// get rounded strings
// if showUncertainty is true, add brackets around the string
final String valueString = (showUncertainty ? "(" : "")
@@ -332,7 +332,7 @@ public final class LinearUnitValue {
final String baseValueString = (showUncertainty ? "(" : "")
+ baseValue.toString(showUncertainty, roundingMode)
+ (showUncertainty ? ")" : "");
-
+
// create string
if (chosenName == null)
return String.format("%s unnamed unit (= %s %s)", valueString,
diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java
index 05e82ba..7841987 100644
--- a/src/main/java/sevenUnits/unit/Metric.java
+++ b/src/main/java/sevenUnits/unit/Metric.java
@@ -59,13 +59,13 @@ public final class Metric {
.valueOf("Information", "Info"); // non-SI
public static final BaseDimension CURRENCY = BaseDimension
.valueOf("Currency", "$$"); // non-SI
-
+
// You may NOT get SI.BaseDimensions instances!
private BaseDimensions() {
throw new AssertionError();
}
}
-
+
/// base units of the SI
// suppressing warnings since these are the same object, but in a different
/// form (class)
@@ -89,16 +89,16 @@ public final class Metric {
.valueOf(BaseDimensions.INFORMATION, "bit", "b");
public static final BaseUnit DOLLAR = BaseUnit
.valueOf(BaseDimensions.CURRENCY, "dollar", "$");
-
+
public static final Set<BaseUnit> BASE_UNITS = Set.of(METRE, KILOGRAM,
SECOND, AMPERE, KELVIN, MOLE, CANDELA, BIT);
-
+
// You may NOT get SI.BaseUnits instances!
private BaseUnits() {
throw new AssertionError();
}
}
-
+
/**
* Constants that relate to the SI or other systems.
*
@@ -109,7 +109,7 @@ public final class Metric {
public static final LinearUnit EARTH_GRAVITY = METRE.dividedBy(SECOND)
.dividedBy(SECOND).times(9.80665);
}
-
+
// dimensions used in the SI, as ObjectProducts
public static final class Dimensions {
public static final ObjectProduct<BaseDimension> EMPTY = ObjectProduct
@@ -139,7 +139,7 @@ public final class Metric {
public static final ObjectProduct<BaseDimension> CURRENCY = ObjectProduct
.oneOf(BaseDimensions.CURRENCY)
.withName(NameSymbol.ofName("Currency"));
-
+
// derived dimensions without named SI units
public static final ObjectProduct<BaseDimension> AREA = LENGTH
.times(LENGTH);
@@ -175,7 +175,7 @@ public final class Metric {
.dividedBy(LENGTH);
public static final ObjectProduct<BaseDimension> SOLID_ANGLE = AREA
.dividedBy(AREA);
-
+
// derived dimensions with named SI units
public static final ObjectProduct<BaseDimension> FREQUENCY = EMPTY
.dividedBy(TIME);
@@ -209,17 +209,17 @@ public final class Metric {
.dividedBy(MASS);
public static final ObjectProduct<BaseDimension> CATALYTIC_ACTIVITY = QUANTITY
.dividedBy(TIME);
-
+
// You may NOT get SI.Dimension instances!
private Dimensions() {
throw new AssertionError();
}
}
-
+
/// The units of the SI
public static final LinearUnit ONE = LinearUnit
.valueOf(ObjectProduct.empty(), 1);
-
+
public static final LinearUnit METRE = BaseUnits.METRE.asLinearUnit()
.withName(NameSymbol.of("metre", "m", "meter"));
public static final LinearUnit KILOGRAM = BaseUnits.KILOGRAM.asLinearUnit()
@@ -241,7 +241,7 @@ public final class Metric {
// Non-base units
public static final LinearUnit RADIAN = METRE.dividedBy(METRE)
.withName(NameSymbol.of("radian", "rad"));
-
+
public static final LinearUnit STERADIAN = RADIAN.times(RADIAN)
.withName(NameSymbol.of("steradian", "sr"));
public static final LinearUnit HERTZ = ONE.dividedBy(SECOND)
@@ -290,7 +290,7 @@ public final class Metric {
// common derived units included for convenience
public static final LinearUnit GRAM = KILOGRAM.dividedBy(1000)
.withName(NameSymbol.of("gram", "g"));
-
+
public static final LinearUnit SQUARE_METRE = METRE.toExponent(2)
.withName(NameSymbol.of("square metre", "m^2", "square meter",
"metre squared", "meter squared"));
@@ -305,7 +305,7 @@ public final class Metric {
.fromConversionFunctions(KELVIN.getBase(), tempK -> tempK - 273.15,
tempC -> tempC + 273.15)
.withName(NameSymbol.of("degree Celsius", "\u00B0C"));
-
+
public static final LinearUnit MINUTE = SECOND.times(60)
.withName(NameSymbol.of("minute", "min"));
public static final LinearUnit HOUR = MINUTE.times(60)
@@ -349,7 +349,7 @@ public final class Metric {
.fromConversionFunctions(ONE.getBase(), pr -> 10 * Math.log10(pr),
dB -> Math.pow(10, dB / 10))
.withName(NameSymbol.of("decibel", "dB"));
-
+
/// The prefixes of the SI
// expanding decimal prefixes
public static final UnitPrefix KILO = UnitPrefix.valueOf(1e3)
@@ -368,7 +368,7 @@ public final class Metric {
.withName(NameSymbol.of("zetta", "Z"));
public static final UnitPrefix YOTTA = UnitPrefix.valueOf(1e24)
.withName(NameSymbol.of("yotta", "Y"));
-
+
// contracting decimal prefixes
public static final UnitPrefix MILLI = UnitPrefix.valueOf(1e-3)
.withName(NameSymbol.of("milli", "m"));
@@ -386,7 +386,7 @@ public final class Metric {
.withName(NameSymbol.of("zepto", "z"));
public static final UnitPrefix YOCTO = UnitPrefix.valueOf(1e-24)
.withName(NameSymbol.of("yocto", "y"));
-
+
// prefixes that don't match the pattern of thousands
public static final UnitPrefix DEKA = UnitPrefix.valueOf(1e1)
.withName(NameSymbol.of("deka", "da", "deca", "D"));
@@ -408,7 +408,7 @@ public final class Metric {
.withName(NameSymbol.of("pebi", "Pi"));
public static final UnitPrefix EXBI = PEBI.times(1024)
.withName(NameSymbol.of("exbi", "Ei"));
-
+
// a few prefixed units
public static final LinearUnit MICROMETRE = Metric.METRE
.withPrefix(Metric.MICRO);
@@ -418,7 +418,7 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGAMETRE = Metric.METRE
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICROLITRE = Metric.LITRE
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLILITRE = Metric.LITRE
@@ -427,7 +427,7 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGALITRE = Metric.LITRE
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICROSECOND = Metric.SECOND
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLISECOND = Metric.SECOND
@@ -436,14 +436,14 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGASECOND = Metric.SECOND
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICROGRAM = Metric.GRAM
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLIGRAM = Metric.GRAM
.withPrefix(Metric.MILLI);
public static final LinearUnit MEGAGRAM = Metric.GRAM
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICRONEWTON = Metric.NEWTON
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLINEWTON = Metric.NEWTON
@@ -452,7 +452,7 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGANEWTON = Metric.NEWTON
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICROJOULE = Metric.JOULE
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLIJOULE = Metric.JOULE
@@ -461,7 +461,7 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGAJOULE = Metric.JOULE
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICROWATT = Metric.WATT
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLIWATT = Metric.WATT
@@ -470,7 +470,7 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGAWATT = Metric.WATT
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICROCOULOMB = Metric.COULOMB
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLICOULOMB = Metric.COULOMB
@@ -479,12 +479,12 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGACOULOMB = Metric.COULOMB
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit MICROAMPERE = Metric.AMPERE
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLIAMPERE = Metric.AMPERE
.withPrefix(Metric.MILLI);
-
+
public static final LinearUnit MICROVOLT = Metric.VOLT
.withPrefix(Metric.MICRO);
public static final LinearUnit MILLIVOLT = Metric.VOLT
@@ -493,16 +493,16 @@ public final class Metric {
.withPrefix(Metric.KILO);
public static final LinearUnit MEGAVOLT = Metric.VOLT
.withPrefix(Metric.MEGA);
-
+
public static final LinearUnit KILOOHM = Metric.OHM.withPrefix(Metric.KILO);
public static final LinearUnit MEGAOHM = Metric.OHM.withPrefix(Metric.MEGA);
-
+
// sets of prefixes
public static final Set<UnitPrefix> ALL_PREFIXES = Set.of(DEKA, HECTO, KILO,
MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI, MICRO,
NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO, KIBI, MEBI, GIBI, TEBI, PEBI,
EXBI);
-
+
public static final Set<UnitPrefix> DECIMAL_PREFIXES = Set.of(DEKA, HECTO,
KILO, MEGA, GIGA, TERA, PETA, EXA, ZETTA, YOTTA, DECI, CENTI, MILLI,
MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO);
@@ -514,7 +514,7 @@ public final class Metric {
TEBI, PEBI, EXBI);
public static final Set<UnitPrefix> REDUCING_PREFIXES = Set.of(DECI, CENTI,
MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO);
-
+
// You may NOT get SI instances!
private Metric() {
throw new AssertionError();
diff --git a/src/main/java/sevenUnits/unit/MultiUnit.java b/src/main/java/sevenUnits/unit/MultiUnit.java
index bc240e3..950c547 100644
--- a/src/main/java/sevenUnits/unit/MultiUnit.java
+++ b/src/main/java/sevenUnits/unit/MultiUnit.java
@@ -39,7 +39,7 @@ public final class MultiUnit extends Unitlike<List<Double>> {
public static final MultiUnit of(LinearUnit... units) {
return of(Arrays.asList(units));
}
-
+
/**
* Creates a {@code MultiUnit} from its units. It will not have a name or
* symbol.
@@ -57,12 +57,12 @@ public final class MultiUnit extends Unitlike<List<Double>> {
}
return new MultiUnit(new ArrayList<>(units), unitBase, NameSymbol.EMPTY);
}
-
+
/**
* The units that make up this value.
*/
private final List<LinearUnit> units;
-
+
/**
* Creates a {@code MultiUnit}.
*
@@ -73,24 +73,24 @@ public final class MultiUnit extends Unitlike<List<Double>> {
super(unitBase, ns);
this.units = units;
}
-
+
@Override
protected List<Double> convertFromBase(double value) {
final List<Double> values = new ArrayList<>(this.units.size());
double temp = value;
-
+
for (final LinearUnit unit : this.units.subList(0,
this.units.size() - 1)) {
values.add(Math.floor(temp / unit.getConversionFactor()));
temp %= unit.getConversionFactor();
}
-
+
values.add(this.units.size() - 1,
this.units.get(this.units.size() - 1).convertFromBase(temp));
-
+
return values;
}
-
+
/**
* Converts a value expressed in this unitlike form to a value expressed in
* {@code other}.
@@ -99,7 +99,7 @@ public final class MultiUnit extends Unitlike<List<Double>> {
* {@code other.convertFromBase(this.convertToBase(value))}.
* Therefore, overriding either of those methods will change the
* output of this method.
- *
+ *
* @param other unit to convert to
* @param value value to convert
* @return converted value
@@ -115,10 +115,10 @@ public final class MultiUnit extends Unitlike<List<Double>> {
for (final double d : values) {
valueList.add(d);
}
-
+
return this.convertTo(other, valueList);
}
-
+
/**
* Converts a value expressed in this unitlike form to a value expressed in
* {@code other}.
@@ -127,7 +127,7 @@ public final class MultiUnit extends Unitlike<List<Double>> {
* {@code other.convertFromBase(this.convertToBase(value))}.
* Therefore, overriding either of those methods will change the
* output of this method.
- *
+ *
* @param other unit to convert to
* @param value value to convert
* @return converted value
@@ -142,16 +142,16 @@ public final class MultiUnit extends Unitlike<List<Double>> {
for (final double d : values) {
valueList.add(d);
}
-
+
return this.convertTo(other, valueList);
}
-
+
@Override
protected double convertToBase(List<Double> value) {
if (value.size() != this.units.size())
throw new IllegalArgumentException("Wrong number of values for "
+ this.units.size() + "-unit MultiUnit.");
-
+
double baseValue = 0;
for (int i = 0; i < this.units.size(); i++) {
baseValue += value.get(i) * this.units.get(i).getConversionFactor();
diff --git a/src/main/java/sevenUnits/unit/USCustomary.java b/src/main/java/sevenUnits/unit/USCustomary.java
index 459071f..fce829e 100644
--- a/src/main/java/sevenUnits/unit/USCustomary.java
+++ b/src/main/java/sevenUnits/unit/USCustomary.java
@@ -30,10 +30,14 @@ public final class USCustomary {
* @since 2019-11-08
*/
public static final class Area {
- public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT.times(Length.SURVEY_FOOT);
- public static final LinearUnit SQUARE_CHAIN = Length.SURVEY_CHAIN.times(Length.SURVEY_CHAIN);
- public static final LinearUnit ACRE = Length.SURVEY_CHAIN.times(Length.SURVEY_FURLONG);
- public static final LinearUnit SECTION = Length.SURVEY_MILE.times(Length.SURVEY_MILE);
+ public static final LinearUnit SQUARE_SURVEY_FOOT = Length.SURVEY_FOOT
+ .times(Length.SURVEY_FOOT);
+ public static final LinearUnit SQUARE_CHAIN = Length.SURVEY_CHAIN
+ .times(Length.SURVEY_CHAIN);
+ public static final LinearUnit ACRE = Length.SURVEY_CHAIN
+ .times(Length.SURVEY_FURLONG);
+ public static final LinearUnit SECTION = Length.SURVEY_MILE
+ .times(Length.SURVEY_MILE);
public static final LinearUnit SURVEY_TOWNSHIP = SECTION.times(36);
}
@@ -52,8 +56,10 @@ public final class USCustomary {
public static final LinearUnit YARD = BritishImperial.Length.YARD;
public static final LinearUnit MILE = BritishImperial.Length.MILE;
- public static final LinearUnit SURVEY_FOOT = Metric.METRE.times(1200.0 / 3937.0);
- public static final LinearUnit SURVEY_LINK = SURVEY_FOOT.times(33.0 / 50.0);
+ public static final LinearUnit SURVEY_FOOT = Metric.METRE
+ .times(1200.0 / 3937.0);
+ public static final LinearUnit SURVEY_LINK = SURVEY_FOOT
+ .times(33.0 / 50.0);
public static final LinearUnit SURVEY_ROD = SURVEY_FOOT.times(16.5);
public static final LinearUnit SURVEY_CHAIN = SURVEY_ROD.times(4);
public static final LinearUnit SURVEY_FURLONG = SURVEY_CHAIN.times(10);
@@ -97,7 +103,8 @@ public final class USCustomary {
public static final LinearUnit CUBIC_YARD = Length.YARD.toExponent(3);
public static final LinearUnit ACRE_FOOT = Area.ACRE.times(Length.FOOT);
- public static final LinearUnit MINIM = Metric.LITRE.withPrefix(Metric.MICRO).times(61.611519921875);
+ public static final LinearUnit MINIM = Metric.LITRE
+ .withPrefix(Metric.MICRO).times(61.611519921875);
public static final LinearUnit FLUID_DRAM = MINIM.times(60);
public static final LinearUnit TEASPOON = MINIM.times(80);
public static final LinearUnit TABLESPOON = TEASPOON.times(3);
@@ -112,7 +119,8 @@ public final class USCustomary {
public static final LinearUnit OIL_BARREL = GALLON.times(42);
public static final LinearUnit HOGSHEAD = GALLON.times(63);
- public static final LinearUnit DRY_PINT = Metric.LITRE.times(0.5506104713575);
+ public static final LinearUnit DRY_PINT = Metric.LITRE
+ .times(0.5506104713575);
public static final LinearUnit DRY_QUART = DRY_PINT.times(2);
public static final LinearUnit DRY_GALLON = DRY_QUART.times(4);
public static final LinearUnit PECK = DRY_GALLON.times(2);
@@ -128,8 +136,10 @@ public final class USCustomary {
public static final LinearUnit KILOCALORIE = BritishImperial.KILOCALORIE;
public static final LinearUnit FOOT_POUND = POUND_FORCE.times(Length.FOOT);
- public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE).dividedBy(Metric.MINUTE).times(33000);
- public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE.dividedBy(Length.INCH.toExponent(2));
+ public static final LinearUnit HORSEPOWER = Length.FOOT.times(POUND_FORCE)
+ .dividedBy(Metric.MINUTE).times(33000);
+ public static final LinearUnit POUND_PER_SQUARE_INCH = POUND_FORCE
+ .dividedBy(Length.INCH.toExponent(2));
public static final Unit FAHRENHEIT = BritishImperial.FAHRENHEIT;
}
diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java
index 14478ba..59e928a 100644
--- a/src/main/java/sevenUnits/unit/Unit.java
+++ b/src/main/java/sevenUnits/unit/Unit.java
@@ -59,7 +59,7 @@ public abstract class Unit implements Nameable {
final DoubleUnaryOperator converterTo) {
return new FunctionalUnit(base, converterFrom, converterTo);
}
-
+
/**
* Returns a unit from its base and the functions it uses to convert to and
* from its base.
@@ -87,28 +87,28 @@ public abstract class Unit implements Nameable {
final DoubleUnaryOperator converterTo, final NameSymbol ns) {
return new FunctionalUnit(base, converterFrom, converterTo, ns);
}
-
+
/**
* The combination of units that this unit is based on.
*
* @since 2019-10-16
*/
private final ObjectProduct<BaseUnit> unitBase;
-
+
/**
* This unit's name(s) and symbol
*
* @since 2020-09-07
*/
private final NameSymbol nameSymbol;
-
+
/**
* Cache storing the result of getDimension()
*
* @since 2019-10-16
*/
private transient ObjectProduct<BaseDimension> dimension = null;
-
+
/**
* A constructor that constructs {@code BaseUnit} instances.
*
@@ -121,7 +121,7 @@ public abstract class Unit implements Nameable {
throw new AssertionError();
this.nameSymbol = nameSymbol;
}
-
+
/**
* Creates the {@code Unit}.
*
@@ -135,7 +135,7 @@ public abstract class Unit implements Nameable {
"unitBase may not be null");
this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null");
}
-
+
/**
* @return this unit as a {@link Unitlike}
* @since 2020-09-07
@@ -144,7 +144,7 @@ public abstract class Unit implements Nameable {
return Unitlike.fromConversionFunctions(this.getBase(),
this::convertFromBase, this::convertToBase, this.getNameSymbol());
}
-
+
/**
* Checks if a value expressed in this unit can be converted to a value
* expressed in {@code other}
@@ -159,7 +159,7 @@ public abstract class Unit implements Nameable {
Objects.requireNonNull(other, "other must not be null.");
return Objects.equals(this.getBase(), other.getBase());
}
-
+
/**
* Checks if a value expressed in this unit can be converted to a value
* expressed in {@code other}
@@ -174,7 +174,7 @@ public abstract class Unit implements Nameable {
Objects.requireNonNull(other, "other must not be null.");
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.
@@ -190,14 +190,14 @@ public abstract class Unit implements Nameable {
*
* @implSpec This method is used by {@link #convertTo}, and its behaviour
* affects the behaviour of {@code convertTo}.
- *
+ *
* @param value value expressed in <b>base</b> unit
* @return value expressed in <b>this</b> unit
* @since 2018-12-22
* @since v0.1.0
*/
protected abstract double convertFromBase(double value);
-
+
/**
* Converts a value expressed in this unit to a value expressed in
* {@code other}.
@@ -206,7 +206,7 @@ public abstract class Unit implements Nameable {
* {@code other.convertFromBase(this.convertToBase(value))}.
* Therefore, overriding either of those methods will change the
* output of this method.
- *
+ *
* @param other unit to convert to
* @param value value to convert
* @return converted value
@@ -224,7 +224,7 @@ public abstract class Unit implements Nameable {
throw new IllegalArgumentException(
String.format("Cannot convert from %s to %s.", this, other));
}
-
+
/**
* Converts a value expressed in this unit to a value expressed in
* {@code other}.
@@ -252,7 +252,7 @@ public abstract class Unit implements Nameable {
throw new IllegalArgumentException(
String.format("Cannot convert from %s to %s.", this, other));
}
-
+
/**
* Converts from a value expressed in this unit to a value expressed in this
* unit's base unit.
@@ -268,14 +268,14 @@ public abstract class Unit implements Nameable {
*
* @implSpec This method is used by {@link #convertTo}, and its behaviour
* affects the behaviour of {@code convertTo}.
- *
+ *
* @param value value expressed in <b>this</b> unit
* @return value expressed in <b>base</b> unit
* @since 2018-12-22
* @since v0.1.0
*/
protected abstract double convertToBase(double value);
-
+
/**
* @return combination of units that this unit is based on
* @since 2018-12-22
@@ -284,7 +284,7 @@ public abstract class Unit implements Nameable {
public final ObjectProduct<BaseUnit> getBase() {
return this.unitBase;
}
-
+
/**
* @return dimension measured by this unit
* @since 2018-12-22
@@ -294,16 +294,16 @@ public abstract class Unit implements Nameable {
if (this.dimension == null) {
final Map<BaseUnit, Integer> mapping = this.unitBase.exponentMap();
final Map<BaseDimension, Integer> dimensionMap = new HashMap<>();
-
+
for (final BaseUnit key : mapping.keySet()) {
dimensionMap.put(key.getBaseDimension(), mapping.get(key));
}
-
+
this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
}
return this.dimension;
}
-
+
/**
* @return the nameSymbol
* @since 2020-09-07
@@ -312,7 +312,7 @@ public abstract class Unit implements Nameable {
public final NameSymbol getNameSymbol() {
return this.nameSymbol;
}
-
+
/**
* Returns true iff this unit is metric.
* <p>
@@ -337,18 +337,18 @@ public abstract class Unit implements Nameable {
if (!(this instanceof LinearUnit))
return false;
final LinearUnit linear = (LinearUnit) this;
-
+
// second condition - check that
for (final BaseUnit b : linear.getBase().getBaseSet()) {
if (!Metric.BaseUnits.BASE_UNITS.contains(b))
return false;
}
-
+
// third condition - check that conversion factor is a power of 10
return DecimalComparison
.equals(Math.log10(linear.getConversionFactor()) % 1.0, 0);
}
-
+
/**
* @return a string representing this unit's definition
* @since 2022-03-10
@@ -360,7 +360,7 @@ public abstract class Unit implements Nameable {
return "derived from "
+ this.getBase().toString(BaseUnit::getShortName);
}
-
+
/**
* @return a string containing both this unit's name and its definition
* @since 2022-03-10
@@ -368,7 +368,7 @@ public abstract class Unit implements Nameable {
public final String toFullString() {
return this.toString() + " (" + this.toDefinitionString() + ")";
}
-
+
@Override
public String toString() {
if (this.nameSymbol.getPrimaryName().isPresent()
@@ -378,7 +378,7 @@ public abstract class Unit implements Nameable {
else
return this.getName();
}
-
+
/**
* @param ns name(s) and symbol to use
* @return a copy of this unit with provided name(s) and symbol
diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java
index 12b78a7..0120067 100644
--- a/src/main/java/sevenUnits/unit/UnitDatabase.java
+++ b/src/main/java/sevenUnits/unit/UnitDatabase.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 Adrien Hopkins
+ * Copyright (C) 2018-2024 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
@@ -123,7 +123,7 @@ public final class UnitDatabase {
implements Entry<String, Unit> {
private final String key;
private final Unit value;
-
+
/**
* Creates the {@code PrefixedUnitEntry}.
*
@@ -136,7 +136,7 @@ public final class UnitDatabase {
this.key = key;
this.value = value;
}
-
+
/**
* @since 2019-05-03
*/
@@ -148,17 +148,17 @@ public final class UnitDatabase {
return Objects.equals(this.getKey(), other.getKey())
&& Objects.equals(this.getValue(), other.getValue());
}
-
+
@Override
public String getKey() {
return this.key;
}
-
+
@Override
public Unit getValue() {
return this.value;
}
-
+
/**
* @since 2019-05-03
*/
@@ -168,13 +168,13 @@ public final class UnitDatabase {
^ (this.getValue() == null ? 0
: this.getValue().hashCode());
}
-
+
@Override
public Unit setValue(final Unit value) {
throw new UnsupportedOperationException(
"Cannot set value in an immutable entry");
}
-
+
/**
* Returns a string representation of the entry. The format of the
* string is the string representation of the key, then the equals
@@ -188,7 +188,7 @@ public final class UnitDatabase {
return this.getKey() + "=" + this.getValue();
}
}
-
+
/**
* An iterator that iterates over the units of a
* {@code PrefixedUnitNameSet}.
@@ -203,12 +203,12 @@ public final class UnitDatabase {
private int unitNamePosition = 0;
// the indices of the prefixes attached to the current unit
private final List<Integer> prefixCoordinates = new ArrayList<>();
-
+
// values from the unit entry set
private final Map<String, Unit> map;
private transient final List<String> unitNames;
private transient final List<String> prefixNames;
-
+
/**
* Creates the
* {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
@@ -221,7 +221,7 @@ public final class UnitDatabase {
this.unitNames = new ArrayList<>(map.units.keySet());
this.prefixNames = new ArrayList<>(map.prefixes.keySet());
}
-
+
/**
* @return current unit name
* @since 2019-04-14
@@ -233,10 +233,10 @@ public final class UnitDatabase {
unitName.append(this.prefixNames.get(i));
}
unitName.append(this.unitNames.get(this.unitNamePosition));
-
+
return unitName.toString();
}
-
+
@Override
public boolean hasNext() {
if (this.unitNames.isEmpty())
@@ -249,7 +249,7 @@ public final class UnitDatabase {
return true;
}
}
-
+
/**
* Changes this iterator's position to the next available one.
*
@@ -258,11 +258,11 @@ public final class UnitDatabase {
*/
private void incrementPosition() {
this.unitNamePosition++;
-
+
if (this.unitNamePosition >= this.unitNames.size()) {
// we have used all of our units, go to a different prefix
this.unitNamePosition = 0;
-
+
// if the prefix coordinates are empty, then set it to [0]
if (this.prefixCoordinates.isEmpty()) {
this.prefixCoordinates.add(0, 0);
@@ -271,7 +271,7 @@ public final class UnitDatabase {
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()) {
@@ -279,7 +279,7 @@ public final class UnitDatabase {
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
@@ -290,18 +290,18 @@ public final class UnitDatabase {
}
}
}
-
+
@Override
public Entry<String, Unit> next() {
// get next element
final Entry<String, Unit> nextEntry = this.peek();
-
+
// iterate to next position
this.incrementPosition();
-
+
return nextEntry;
}
-
+
/**
* @return the next element in the iterator, without iterating over
* it
@@ -310,7 +310,7 @@ public final class UnitDatabase {
private Entry<String, Unit> peek() {
if (!this.hasNext())
throw new NoSuchElementException("No units left!");
-
+
// if I have prefixes, ensure I'm not using a nonlinear unit
// since all of the unprefixed stuff is done, just remove
// nonlinear units
@@ -321,12 +321,12 @@ public final class UnitDatabase {
this.unitNames.remove(this.unitNamePosition);
}
}
-
+
final String nextName = this.getCurrentUnitName();
-
+
return new PrefixedUnitEntry(nextName, this.map.get(nextName));
}
-
+
/**
* Returns a string representation of the object. The exact details
* of the representation are unspecified and subject to change.
@@ -340,10 +340,10 @@ public final class UnitDatabase {
this.peek());
}
}
-
+
// the map that created this set
private final PrefixedUnitMap map;
-
+
/**
* Creates the {@code PrefixedUnitNameSet}.
*
@@ -354,31 +354,31 @@ public final class UnitDatabase {
public PrefixedUnitEntrySet(final PrefixedUnitMap map) {
this.map = map;
}
-
+
@Override
public boolean add(final Map.Entry<String, Unit> e) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public boolean addAll(
final Collection<? extends Map.Entry<String, Unit>> c) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public void clear() {
throw new UnsupportedOperationException(
"Cannot clear an immutable set");
}
-
+
@Override
public boolean contains(final Object o) {
// get the entry
final Entry<String, Unit> entry;
-
+
try {
// This is OK because I'm in a try-catch block, catching the
// exact exception that would be thrown.
@@ -389,11 +389,11 @@ public final class UnitDatabase {
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)
@@ -401,42 +401,42 @@ public final class UnitDatabase {
return false;
return true;
}
-
+
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
-
+
@Override
public Iterator<Entry<String, Unit>> iterator() {
return new PrefixedUnitEntryIterator(this.map);
}
-
+
@Override
public boolean remove(final Object o) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeIf(
final Predicate<? super Entry<String, Unit>> filter) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean retainAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public int size() {
if (this.map.units.isEmpty())
@@ -449,7 +449,7 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -462,7 +462,7 @@ public final class UnitDatabase {
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -475,7 +475,7 @@ public final class UnitDatabase {
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
}
-
+
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
@@ -486,7 +486,7 @@ public final class UnitDatabase {
this.map.units, this.map.prefixes);
}
}
-
+
/**
* The class used for unit name sets.
*
@@ -518,12 +518,12 @@ public final class UnitDatabase {
private int unitNamePosition = 0;
// the indices of the prefixes attached to the current unit
private final List<Integer> prefixCoordinates = new ArrayList<>();
-
+
// values from the unit name set
private final Map<String, Unit> map;
private transient final List<String> unitNames;
private transient final List<String> prefixNames;
-
+
/**
* Creates the
* {@code UnitsDatabase.PrefixedUnitMap.PrefixedUnitNameSet.PrefixedUnitNameIterator}.
@@ -536,7 +536,7 @@ public final class UnitDatabase {
this.unitNames = new ArrayList<>(map.units.keySet());
this.prefixNames = new ArrayList<>(map.prefixes.keySet());
}
-
+
/**
* @return current unit name
* @since 2019-04-14
@@ -548,10 +548,10 @@ public final class UnitDatabase {
unitName.append(this.prefixNames.get(i));
}
unitName.append(this.unitNames.get(this.unitNamePosition));
-
+
return unitName.toString();
}
-
+
@Override
public boolean hasNext() {
if (this.unitNames.isEmpty())
@@ -564,7 +564,7 @@ public final class UnitDatabase {
return true;
}
}
-
+
/**
* Changes this iterator's position to the next available one.
*
@@ -573,11 +573,11 @@ public final class UnitDatabase {
*/
private void incrementPosition() {
this.unitNamePosition++;
-
+
if (this.unitNamePosition >= this.unitNames.size()) {
// we have used all of our units, go to a different prefix
this.unitNamePosition = 0;
-
+
// if the prefix coordinates are empty, then set it to [0]
if (this.prefixCoordinates.isEmpty()) {
this.prefixCoordinates.add(0, 0);
@@ -586,7 +586,7 @@ public final class UnitDatabase {
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()) {
@@ -594,7 +594,7 @@ public final class UnitDatabase {
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
@@ -605,16 +605,16 @@ public final class UnitDatabase {
}
}
}
-
+
@Override
public String next() {
final String nextName = this.peek();
-
+
this.incrementPosition();
-
+
return nextName;
}
-
+
/**
* @return the next element in the iterator, without iterating over
* it
@@ -633,10 +633,10 @@ public final class UnitDatabase {
this.unitNames.remove(this.unitNamePosition);
}
}
-
+
return this.getCurrentUnitName();
}
-
+
/**
* Returns a string representation of the object. The exact details
* of the representation are unspecified and subject to change.
@@ -650,10 +650,10 @@ public final class UnitDatabase {
this.peek());
}
}
-
+
// the map that created this set
private final PrefixedUnitMap map;
-
+
/**
* Creates the {@code PrefixedUnitNameSet}.
*
@@ -664,30 +664,30 @@ public final class UnitDatabase {
public PrefixedUnitNameSet(final PrefixedUnitMap map) {
this.map = map;
}
-
+
@Override
public boolean add(final String e) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public boolean addAll(final Collection<? extends String> c) {
throw new UnsupportedOperationException(
"Cannot add to an immutable set");
}
-
+
@Override
public void clear() {
throw new UnsupportedOperationException(
"Cannot clear an immutable set");
}
-
+
@Override
public boolean contains(final Object o) {
return this.map.containsKey(o);
}
-
+
@Override
public boolean containsAll(final Collection<?> c) {
for (final Object o : c)
@@ -695,41 +695,41 @@ public final class UnitDatabase {
return false;
return true;
}
-
+
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
-
+
@Override
public Iterator<String> iterator() {
return new PrefixedUnitNameIterator(this.map);
}
-
+
@Override
public boolean remove(final Object o) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean removeIf(final Predicate<? super String> filter) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public boolean retainAll(final Collection<?> c) {
throw new UnsupportedOperationException(
"Cannot remove from an immutable set");
}
-
+
@Override
public int size() {
if (this.map.units.isEmpty())
@@ -742,7 +742,7 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -754,9 +754,9 @@ public final class UnitDatabase {
// infinite set
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
-
+
}
-
+
/**
* @throws IllegalStateException if the set is infinite in size
*/
@@ -769,7 +769,7 @@ public final class UnitDatabase {
throw new IllegalStateException(
"Cannot make an infinite set into an array.");
}
-
+
@Override
public String toString() {
if (this.map.units.isEmpty() || this.map.prefixes.isEmpty())
@@ -780,7 +780,7 @@ public final class UnitDatabase {
this.map.units, this.map.prefixes);
}
}
-
+
/**
* The units stored in this collection, without prefixes.
*
@@ -788,7 +788,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, Unit> units;
-
+
/**
* The available prefixes for use.
*
@@ -796,12 +796,12 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, UnitPrefix> prefixes;
-
+
// caches
private transient Collection<Unit> values = null;
private transient Set<String> keySet = null;
private transient Set<Entry<String, Unit>> entrySet = null;
-
+
/**
* Creates the {@code PrefixedUnitMap}.
*
@@ -817,50 +817,50 @@ public final class UnitDatabase {
this.units = Collections.unmodifiableMap(units);
this.prefixes = Collections.unmodifiableMap(prefixes);
}
-
+
@Override
public void clear() {
throw new UnsupportedOperationException(
"Cannot clear an immutable map");
}
-
+
@Override
public Unit compute(final String key,
final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
throw new UnsupportedOperationException(
"Cannot edit an immutable map");
}
-
+
@Override
public Unit computeIfAbsent(final String key,
final Function<? super String, ? extends Unit> mappingFunction) {
throw new UnsupportedOperationException(
"Cannot edit an immutable map");
}
-
+
@Override
public Unit computeIfPresent(final String key,
final BiFunction<? super String, ? super Unit, ? extends Unit> remappingFunction) {
throw new UnsupportedOperationException(
"Cannot edit an immutable map");
}
-
+
@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 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)
@@ -879,10 +879,10 @@ public final class UnitDatabase {
}
}
}
-
+
return longestPrefix != null;
}
-
+
/**
* {@inheritDoc}
*
@@ -895,7 +895,7 @@ public final class UnitDatabase {
public boolean containsValue(final Object value) {
return this.units.containsValue(value);
}
-
+
@Override
public Set<Entry<String, Unit>> entrySet() {
if (this.entrySet == null) {
@@ -903,23 +903,23 @@ public final class UnitDatabase {
}
return this.entrySet;
}
-
+
@Override
public Unit get(final Object key) {
// First, test if there is a unit with the key
if (this.units.containsKey(key))
return this.units.get(key);
-
+
// Next, try to cast it to String
if (!(key instanceof String))
throw new IllegalArgumentException(
"Attempted to obtain a unit using a non-string name.");
final String unitName = (String) key;
-
+
// Then, look for the longest prefix that is attached to a valid unit
String longestPrefix = null;
int longestLength = 0;
-
+
for (final String prefixName : this.prefixes.keySet()) {
// a prefix name is valid if:
// - it is prefixed (i.e. the unit name starts with it)
@@ -938,7 +938,7 @@ public final class UnitDatabase {
}
}
}
-
+
// if none found, returns null
if (longestPrefix == null)
return null;
@@ -949,16 +949,16 @@ public final class UnitDatabase {
// before selecting this prefix
final LinearUnit unit = (LinearUnit) this.get(rest);
final UnitPrefix prefix = this.prefixes.get(longestPrefix);
-
+
return unit.withPrefix(prefix);
}
}
-
+
@Override
public boolean isEmpty() {
return this.units.isEmpty();
}
-
+
@Override
public Set<String> keySet() {
if (this.keySet == null) {
@@ -966,64 +966,64 @@ public final class UnitDatabase {
}
return this.keySet;
}
-
+
@Override
public Unit merge(final String key, final Unit value,
final BiFunction<? super Unit, ? super Unit, ? extends Unit> remappingFunction) {
throw new UnsupportedOperationException(
"Cannot merge into an immutable map");
}
-
+
@Override
public Unit put(final String key, final Unit value) {
throw new UnsupportedOperationException(
"Cannot add entries to an immutable map");
}
-
+
@Override
public void putAll(final Map<? extends String, ? extends Unit> m) {
throw new UnsupportedOperationException(
"Cannot add entries to an immutable map");
}
-
+
@Override
public Unit putIfAbsent(final String key, final Unit value) {
throw new UnsupportedOperationException(
"Cannot add entries to an immutable map");
}
-
+
@Override
public Unit remove(final Object key) {
throw new UnsupportedOperationException(
"Cannot remove entries from an immutable map");
}
-
+
@Override
public boolean remove(final Object key, final Object value) {
throw new UnsupportedOperationException(
"Cannot remove entries from an immutable map");
}
-
+
@Override
public Unit replace(final String key, final Unit value) {
throw new UnsupportedOperationException(
"Cannot replace entries in an immutable map");
}
-
+
@Override
public boolean replace(final String key, final Unit oldValue,
final Unit newValue) {
throw new UnsupportedOperationException(
"Cannot replace entries in an immutable map");
}
-
+
@Override
public void replaceAll(
final BiFunction<? super String, ? super Unit, ? extends Unit> function) {
throw new UnsupportedOperationException(
"Cannot replace entries in an immutable map");
}
-
+
@Override
public int size() {
if (this.units.isEmpty())
@@ -1036,7 +1036,7 @@ public final class UnitDatabase {
return Integer.MAX_VALUE;
}
}
-
+
@Override
public String toString() {
if (this.units.isEmpty() || this.prefixes.isEmpty())
@@ -1046,7 +1046,7 @@ public final class UnitDatabase {
"Infinite map of name-unit entries created from units %s and prefixes %s",
this.units, this.prefixes);
}
-
+
/**
* {@inheritDoc}
*
@@ -1064,20 +1064,20 @@ public final class UnitDatabase {
return this.values;
}
}
-
+
/**
* Replacements done to *all* expression types
*/
private static final Map<Pattern, String> EXPRESSION_REPLACEMENTS = new HashMap<>();
-
+
// add data to expression replacements
static {
// add spaces around operators
- for (final String operator : Arrays.asList("\\*", "/", "\\^")) {
+ for (final String operator : Arrays.asList("\\*", "/", "\\|", "\\^")) {
EXPRESSION_REPLACEMENTS.put(Pattern.compile(operator),
" " + operator + " ");
}
-
+
// replace multiple spaces with a single space
EXPRESSION_REPLACEMENTS.put(Pattern.compile(" +"), " ");
// place brackets around any expression of the form "number unit", with or
@@ -1092,20 +1092,20 @@ public final class UnitDatabase {
// "1e3")
), "\\($1 $2\\)");
}
-
+
/**
* A regular expression that separates names and expressions in unit files.
*/
private static final Pattern NAME_EXPRESSION = Pattern
.compile("(\\S+)\\s+(\\S.*)");
-
+
/**
* Like normal string comparisons, but shorter strings are always less than
* longer strings.
*/
private static final Comparator<String> lengthFirstComparator = Comparator
.comparingInt(String::length).thenComparing(Comparator.naturalOrder());
-
+
/**
* The exponent operator
*
@@ -1132,7 +1132,7 @@ public final class UnitDatabase {
// not a number
throw new IllegalArgumentException("Exponents must be numbers.");
}
-
+
/**
* The exponent operator
*
@@ -1158,7 +1158,7 @@ public final class UnitDatabase {
// not a number
throw new IllegalArgumentException("Exponents must be numbers.");
}
-
+
/**
* @return true if entry represents a removable duplicate entry of map.
* @since 2021-05-22
@@ -1174,7 +1174,7 @@ public final class UnitDatabase {
}
return false;
}
-
+
/**
* The units in this system, excluding prefixes.
*
@@ -1182,7 +1182,7 @@ public final class UnitDatabase {
* @since v0.1.0
*/
private final Map<String, Unit> prefixlessUnits;
-
+
/**
* The unit prefixes in this system.
*
@@ -1190,7 +1190,7 @@ public final class UnitDatabase {
* @since v0.1.0
*/
private final Map<String, UnitPrefix> prefixes;
-
+
/**
* The dimensions in this system.
*
@@ -1198,7 +1198,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, ObjectProduct<BaseDimension>> dimensions;
-
+
/**
* A map mapping strings to units (including prefixes)
*
@@ -1206,7 +1206,7 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final Map<String, Unit> units;
-
+
/**
* The rule that specifies when prefix repetition is allowed. It takes in one
* argument: a list of the prefixes being applied to the unit
@@ -1218,7 +1218,7 @@ public final class UnitDatabase {
* {@code prefixRepetitionRule.test(Arrays.asList(giga, mega, kilo))}
*/
private Predicate<List<UnitPrefix>> prefixRepetitionRule;
-
+
/**
* A parser that can parse unit expressions.
*
@@ -1227,13 +1227,13 @@ public final class UnitDatabase {
*/
private final ExpressionParser<LinearUnit> unitExpressionParser = new ExpressionParser.Builder<>(
this::getLinearUnit).addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
- .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
- .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
- .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2)
- .build();
-
+ .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) -> o1.dividedBy(o2), 3)
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnits, 2).build();
+
/**
* A parser that can parse unit value expressions.
*
@@ -1241,14 +1241,15 @@ public final class UnitDatabase {
*/
private final ExpressionParser<LinearUnitValue> unitValueExpressionParser = new ExpressionParser.Builder<>(
this::getLinearUnitValue)
- .addBinaryOperator("+", (o1, o2) -> o1.plus(o2), 0)
- .addBinaryOperator("-", (o1, o2) -> o1.minus(o2), 0)
- .addBinaryOperator("*", (o1, o2) -> o1.times(o2), 1)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 1)
- .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2)
- .build();
-
+ .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) -> o1.dividedBy(o2), 3)
+ .addBinaryOperator("^", UnitDatabase::exponentiateUnitValues, 2)
+ .build();
+
/**
* A parser that can parse unit prefix expressions
*
@@ -1256,13 +1257,16 @@ public final class UnitDatabase {
* @since v0.2.0
*/
private final ExpressionParser<UnitPrefix> prefixExpressionParser = new ExpressionParser.Builder<>(
- this::getPrefix).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
- .addBinaryOperator("^",
- (o1, o2) -> o1.toExponent(o2.getMultiplier()), 1)
- .build();
-
+ this::getPrefix).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) -> o1.dividedBy(o2), 3)
+ .addBinaryOperator("^", (o1, o2) -> o1.toExponent(o2.getMultiplier()),
+ 2)
+ .build();
+
/**
* A parser that can parse unit dimension expressions.
*
@@ -1271,9 +1275,14 @@ public final class UnitDatabase {
*/
private final ExpressionParser<ObjectProduct<BaseDimension>> unitDimensionParser = new ExpressionParser.Builder<>(
this::getDimension).addBinaryOperator("*", (o1, o2) -> o1.times(o2), 0)
- .addSpaceFunction("*")
- .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0).build();
-
+ .addSpaceFunction("*")
+ .addBinaryOperator("/", (o1, o2) -> o1.dividedBy(o2), 0)
+ .addBinaryOperator("|", (o1, o2) -> o1.dividedBy(o2), 2)
+ .addNumericOperator("^", (o1, o2) -> {
+ int exponent = (int) Math.round(o2.value());
+ return o1.toExponent(exponent);
+ }, 1).build();
+
/**
* Creates the {@code UnitsDatabase}.
*
@@ -1283,7 +1292,7 @@ public final class UnitDatabase {
public UnitDatabase() {
this(prefixes -> true);
}
-
+
/**
* Creates the {@code UnitsDatabase}
*
@@ -1301,7 +1310,7 @@ public final class UnitDatabase {
entry -> this.prefixRepetitionRule
.test(this.getPrefixesFromName(entry.getKey())));
}
-
+
/**
* Adds a unit dimension to the database.
*
@@ -1319,7 +1328,7 @@ public final class UnitDatabase {
.withName(dimension.getNameSymbol().withExtraName(name));
this.dimensions.put(name, namedDimension);
}
-
+
/**
* Adds to the list from a line in a unit dimension file.
*
@@ -1338,7 +1347,7 @@ public final class UnitDatabase {
lineCounter);
return;
}
-
+
// divide line into name and expression
final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
@@ -1347,12 +1356,12 @@ public final class UnitDatabase {
lineCounter));
final String name = lineMatcher.group(1);
final String expression = lineMatcher.group(2);
-
+
// if (name.endsWith(" ")) {
// System.err.printf("Warning - line %d's dimension name ends in a space",
// lineCounter);
// }
-
+
// if expression is "!", search for an existing dimension
// if no unit found, throw an error
if (expression.equals("!")) {
@@ -1368,11 +1377,11 @@ public final class UnitDatabase {
System.err.printf("Parsing error on line %d:%n", lineCounter);
throw e;
}
-
+
this.addDimension(name, dimension);
}
}
-
+
/**
* Adds a unit prefix to the database.
*
@@ -1389,7 +1398,7 @@ public final class UnitDatabase {
this.prefixes.put(Objects.requireNonNull(name, "name must not be null."),
namedPrefix);
}
-
+
/**
* Adds a unit to the database.
*
@@ -1406,7 +1415,7 @@ public final class UnitDatabase {
this.prefixlessUnits.put(
Objects.requireNonNull(name, "name must not be null."), namedUnit);
}
-
+
/**
* Adds to the list from a line in a unit file.
*
@@ -1425,7 +1434,7 @@ public final class UnitDatabase {
lineCounter);
return;
}
-
+
// divide line into name and expression
final Matcher lineMatcher = NAME_EXPRESSION.matcher(line);
if (!lineMatcher.matches())
@@ -1433,15 +1442,15 @@ public final class UnitDatabase {
"Error at line %d: Lines of a unit file must consist of a unit name, then spaces or tabs, then a unit expression.",
lineCounter));
final String name = lineMatcher.group(1);
-
+
final String expression = lineMatcher.group(2);
-
+
// this code should never occur
// 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("!")) {
@@ -1474,7 +1483,7 @@ public final class UnitDatabase {
}
}
}
-
+
/**
* Removes all units, prefixes and dimensions from this database.
*
@@ -1485,7 +1494,7 @@ public final class UnitDatabase {
this.prefixes.clear();
this.prefixlessUnits.clear();
}
-
+
/**
* Tests if the database has a unit dimension with this name.
*
@@ -1497,7 +1506,7 @@ public final class UnitDatabase {
public boolean containsDimensionName(final String name) {
return this.dimensions.containsKey(name);
}
-
+
/**
* Tests if the database has a unit prefix with this name.
*
@@ -1509,7 +1518,7 @@ public final class UnitDatabase {
public boolean containsPrefixName(final String name) {
return this.prefixes.containsKey(name);
}
-
+
/**
* Tests if the database has a unit with this name, taking prefixes into
* consideration
@@ -1522,7 +1531,7 @@ public final class UnitDatabase {
public boolean containsUnitName(final String name) {
return this.units.containsKey(name);
}
-
+
/**
* @return a map mapping dimension names to dimensions
* @since 2019-04-13
@@ -1531,7 +1540,7 @@ public final class UnitDatabase {
public Map<String, ObjectProduct<BaseDimension>> dimensionMap() {
return Collections.unmodifiableMap(this.dimensions);
}
-
+
/**
* Evaluates a unit expression, following the same rules as
* {@link #getUnitFromExpression}.
@@ -1542,46 +1551,42 @@ public final class UnitDatabase {
*/
public LinearUnitValue evaluateUnitExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias, or a number with precision first
if (this.containsUnitName(expression))
return this.getLinearUnitValue(expression);
-
+
// force operators to have spaces
String modifiedExpression = expression;
modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
modifiedExpression = modifiedExpression.replaceAll("-", " - ");
-
+
// format expression
for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
.entrySet()) {
modifiedExpression = replacement.getKey().matcher(modifiedExpression)
.replaceAll(replacement.getValue());
}
-
+
// the previous operation breaks negative numbers, fix them!
// (i.e. -2 becomes - 2)
// FIXME the previous operaton also breaks stuff like "1e-5"
for (int i = 0; i < modifiedExpression.length(); i++) {
if (modifiedExpression.charAt(i) == '-'
- && (i < 2 || Arrays.asList('+', '-', '*', '/', '^')
+ && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^')
.contains(modifiedExpression.charAt(i - 2)))) {
// found a broken negative number
modifiedExpression = modifiedExpression.substring(0, i + 1)
+ modifiedExpression.substring(i + 2);
}
}
-
+
return this.unitValueExpressionParser.parseExpression(modifiedExpression);
}
-
+
/**
* Gets a unit dimension from the database using its name.
*
- * <p>
- * This method accepts exponents, like "L^3"
- * </p>
- *
* @param name dimension's name
* @return dimension
* @since 2019-03-14
@@ -1589,32 +1594,14 @@ public final class UnitDatabase {
*/
public ObjectProduct<BaseDimension> getDimension(final String name) {
Objects.requireNonNull(name, "name must not be null.");
- if (name.contains("^")) {
- final String[] baseAndExponent = name.split("\\^");
-
- final ObjectProduct<BaseDimension> base = this
- .getDimension(baseAndExponent[0]);
-
- final 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);
- } else {
- final ObjectProduct<BaseDimension> dimension = this.dimensions
- .get(name);
- if (dimension == null)
- throw new NoSuchElementException(
- "No dimension with name \"" + name + "\".");
- else
- return dimension;
- }
+ final ObjectProduct<BaseDimension> dimension = this.dimensions.get(name);
+ if (dimension == null)
+ throw new NoSuchElementException(
+ "No dimension with name \"" + name + "\".");
+ else
+ return dimension;
}
-
+
/**
* Uses the database's data to parse an expression into a unit dimension
* <p>
@@ -1637,25 +1624,24 @@ public final class UnitDatabase {
public ObjectProduct<BaseDimension> getDimensionFromExpression(
final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a dimension as an alias first
if (this.containsDimensionName(expression))
return this.getDimension(expression);
-
+
// force operators to have spaces
String modifiedExpression = expression;
-
+
// format expression
for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
.entrySet()) {
modifiedExpression = replacement.getKey().matcher(modifiedExpression)
.replaceAll(replacement.getValue());
}
- modifiedExpression = modifiedExpression.replaceAll(" *\\^ *", "\\^");
-
+
return this.unitDimensionParser.parseExpression(modifiedExpression);
}
-
+
/**
* Gets a unit. If it is linear, cast it to a LinearUnit and return it.
* Otherwise, throw an {@code IllegalArgumentException}.
@@ -1674,7 +1660,7 @@ public final class UnitDatabase {
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(
@@ -1683,7 +1669,7 @@ public final class UnitDatabase {
} else {
// get a linear unit
final Unit unit = this.getUnit(name);
-
+
if (unit instanceof LinearUnit)
return (LinearUnit) unit;
else
@@ -1691,7 +1677,7 @@ public final class UnitDatabase {
String.format("%s is not a linear unit.", name));
}
}
-
+
/**
* Gets a {@code LinearUnitValue} from a unit name. Nonlinear units will be
* converted to their base units.
@@ -1709,7 +1695,7 @@ public final class UnitDatabase {
return LinearUnitValue.getExact(this.getLinearUnit(name), 1);
}
}
-
+
/**
* Gets a unit prefix from the database from its name
*
@@ -1730,7 +1716,7 @@ public final class UnitDatabase {
return prefix;
}
}
-
+
/**
* Gets all of the prefixes that are on a unit name, in application order.
*
@@ -1741,12 +1727,12 @@ public final class UnitDatabase {
List<UnitPrefix> getPrefixesFromName(final String unitName) {
final List<UnitPrefix> prefixes = new ArrayList<>();
String name = unitName;
-
+
while (!this.prefixlessUnits.containsKey(name)) {
// find the longest prefix
String longestPrefixName = null;
int longestLength = name.length();
-
+
while (longestPrefixName == null) {
longestLength--;
if (longestLength <= 0)
@@ -1756,7 +1742,7 @@ public final class UnitDatabase {
longestPrefixName = name.substring(0, longestLength);
}
}
-
+
// longest prefix found!
final UnitPrefix prefix = this.getPrefix(longestPrefixName);
prefixes.add(0, prefix);
@@ -1764,7 +1750,7 @@ public final class UnitDatabase {
}
return prefixes;
}
-
+
/**
* Gets a unit prefix from a prefix expression
* <p>
@@ -1781,24 +1767,24 @@ public final class UnitDatabase {
*/
public UnitPrefix getPrefixFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias first
if (this.containsUnitName(expression))
return this.getPrefix(expression);
-
+
// force operators to have spaces
String modifiedExpression = expression;
-
+
// format expression
for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
.entrySet()) {
modifiedExpression = replacement.getKey().matcher(modifiedExpression)
.replaceAll(replacement.getValue());
}
-
+
return this.prefixExpressionParser.parseExpression(modifiedExpression);
}
-
+
/**
* @return the prefixRepetitionRule
* @since 2020-08-26
@@ -1806,7 +1792,7 @@ public final class UnitDatabase {
public final Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
return this.prefixRepetitionRule;
}
-
+
/**
* Gets a unit from the database from its name, looking for prefixes.
*
@@ -1839,9 +1825,9 @@ public final class UnitDatabase {
} else
return unit;
}
-
+
}
-
+
/**
* Uses the database's unit data to parse an expression into a unit
* <p>
@@ -1865,38 +1851,38 @@ public final class UnitDatabase {
*/
public Unit getUnitFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
// attempt to get a unit as an alias first
if (this.containsUnitName(expression))
return this.getUnit(expression);
-
+
// force operators to have spaces
String modifiedExpression = expression;
modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
modifiedExpression = modifiedExpression.replaceAll("-", " - ");
-
+
// format expression
for (final Entry<Pattern, String> replacement : EXPRESSION_REPLACEMENTS
.entrySet()) {
modifiedExpression = replacement.getKey().matcher(modifiedExpression)
.replaceAll(replacement.getValue());
}
-
+
// the previous operation breaks negative numbers, fix them!
// (i.e. -2 becomes - 2)
for (int i = 0; i < modifiedExpression.length(); i++) {
if (modifiedExpression.charAt(i) == '-'
- && (i < 2 || Arrays.asList('+', '-', '*', '/', '^')
+ && (i < 2 || Arrays.asList('+', '-', '*', '/', '|', '^')
.contains(modifiedExpression.charAt(i - 2)))) {
// found a broken negative number
modifiedExpression = modifiedExpression.substring(0, i + 1)
+ modifiedExpression.substring(i + 2);
}
}
-
+
return this.unitExpressionParser.parseExpression(modifiedExpression);
}
-
+
/**
* Adds all dimensions from a file, using data from the database to parse
* them.
@@ -1936,7 +1922,7 @@ public final class UnitDatabase {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
-
+
/**
* Adds all dimensions from a {@code InputStream}. Otherwise, works like
* {@link #loadDimensionFile}.
@@ -1952,7 +1938,7 @@ public final class UnitDatabase {
}
}
}
-
+
/**
* Adds all units from a file, using data from the database to parse them.
* <p>
@@ -1991,7 +1977,7 @@ public final class UnitDatabase {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
-
+
/**
* Adds all units from a {@code InputStream}. Otherwise, works like
* {@link #loadUnitsFile}.
@@ -2007,7 +1993,7 @@ public final class UnitDatabase {
}
}
}
-
+
/**
* @param includeDuplicates if false, duplicates are removed from the map
* @return a map mapping prefix names to prefixes
@@ -2022,7 +2008,7 @@ public final class UnitDatabase {
.conditionalExistenceMap(this.prefixes,
entry -> !isRemovableDuplicate(this.prefixes, entry)));
}
-
+
/**
* @param prefixRepetitionRule the prefixRepetitionRule to set
* @since 2020-08-26
@@ -2031,7 +2017,7 @@ public final class UnitDatabase {
Predicate<List<UnitPrefix>> prefixRepetitionRule) {
this.prefixRepetitionRule = prefixRepetitionRule;
}
-
+
/**
* @return a string stating the number of units, prefixes and dimensions in
* the database
@@ -2043,7 +2029,7 @@ public final class UnitDatabase {
this.prefixlessUnits.size(), this.prefixes.size(),
this.dimensions.size());
}
-
+
/**
* Returns a map mapping unit names to units, including units with prefixes.
* <p>
@@ -2075,7 +2061,7 @@ public final class UnitDatabase {
return this.units; // PrefixedUnitMap is immutable so I don't need to make
// an unmodifiable map.
}
-
+
/**
* @param includeDuplicates if true, duplicate units will all exist in the
* map; if false, only one of each unit will exist,
diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java
index e1f7788..9035969 100644
--- a/src/main/java/sevenUnits/unit/UnitPrefix.java
+++ b/src/main/java/sevenUnits/unit/UnitPrefix.java
@@ -40,7 +40,7 @@ public final class UnitPrefix implements Nameable {
public static UnitPrefix valueOf(final double multiplier) {
return new UnitPrefix(multiplier, NameSymbol.EMPTY);
}
-
+
/**
* Gets a {@code UnitPrefix} from a multiplier and a name
*
@@ -55,21 +55,21 @@ public final class UnitPrefix implements Nameable {
return new UnitPrefix(multiplier,
Objects.requireNonNull(ns, "ns must not be null."));
}
-
+
/**
* This prefix's name(s) and symbol.
*
* @since 2022-04-16
*/
private final NameSymbol nameSymbol;
-
+
/**
* The number that this prefix multiplies units by
*
* @since 2019-10-16
*/
private final double multiplier;
-
+
/**
* Creates the {@code DefaultUnitPrefix}.
*
@@ -81,7 +81,7 @@ public final class UnitPrefix implements Nameable {
this.multiplier = multiplier;
this.nameSymbol = ns;
}
-
+
/**
* Divides this prefix by a scalar
*
@@ -92,7 +92,7 @@ public final class UnitPrefix implements Nameable {
public UnitPrefix dividedBy(final double divisor) {
return valueOf(this.getMultiplier() / divisor);
}
-
+
/**
* Divides this prefix by {@code other}.
*
@@ -104,7 +104,7 @@ public final class UnitPrefix implements Nameable {
public UnitPrefix dividedBy(final UnitPrefix other) {
return valueOf(this.getMultiplier() / other.getMultiplier());
}
-
+
/**
* {@inheritDoc}
*
@@ -122,7 +122,7 @@ public final class UnitPrefix implements Nameable {
return DecimalComparison.equals(this.getMultiplier(),
other.getMultiplier());
}
-
+
/**
* @return prefix's multiplier
* @since 2019-11-26
@@ -130,12 +130,12 @@ public final class UnitPrefix implements Nameable {
public double getMultiplier() {
return this.multiplier;
}
-
+
@Override
public NameSymbol getNameSymbol() {
return this.nameSymbol;
}
-
+
/**
* {@inheritDoc}
*
@@ -145,7 +145,7 @@ public final class UnitPrefix implements Nameable {
public int hashCode() {
return DecimalComparison.hash(this.getMultiplier());
}
-
+
/**
* Multiplies this prefix by a scalar
*
@@ -156,7 +156,25 @@ public final class UnitPrefix implements Nameable {
public UnitPrefix times(final double multiplicand) {
return valueOf(this.getMultiplier() * multiplicand);
}
-
+
+ /**
+ * Adds {@code other} to this prefix and returns the result.
+ *
+ * @since 2024-03-03
+ */
+ public UnitPrefix plus(final UnitPrefix other) {
+ return valueOf(this.getMultiplier() + other.getMultiplier());
+ }
+
+ /**
+ * Subtracts {@code other} from this prefix and returns the result.
+ *
+ * @since 2024-03-03
+ */
+ public UnitPrefix minus(final UnitPrefix other) {
+ return valueOf(this.getMultiplier() - other.getMultiplier());
+ }
+
/**
* Multiplies this prefix by {@code other}.
*
@@ -168,7 +186,7 @@ public final class UnitPrefix implements Nameable {
public UnitPrefix times(final UnitPrefix other) {
return valueOf(this.getMultiplier() * other.getMultiplier());
}
-
+
/**
* Raises this prefix to an exponent.
*
@@ -180,7 +198,7 @@ public final class UnitPrefix implements Nameable {
public UnitPrefix toExponent(final double exponent) {
return valueOf(Math.pow(this.getMultiplier(), exponent));
}
-
+
/**
* @return a string describing the prefix and its multiplier
*/
@@ -195,7 +213,7 @@ public final class UnitPrefix implements Nameable {
else
return String.format("Unit Prefix (\u00D7 %s)", this.multiplier);
}
-
+
/**
* @param ns name(s) and symbol to use
* @return copy of this prefix with provided name(s) and symbol
diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java
index 7cebf2d..9a87288 100644
--- a/src/main/java/sevenUnits/unit/UnitType.java
+++ b/src/main/java/sevenUnits/unit/UnitType.java
@@ -33,7 +33,7 @@ import java.util.function.Predicate;
*/
public enum UnitType {
METRIC, SEMI_METRIC, NON_METRIC;
-
+
/**
* Determines which type a unit is. The type will be:
* <ul>
diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java
index 339263d..2d01831 100644
--- a/src/main/java/sevenUnits/unit/UnitValue.java
+++ b/src/main/java/sevenUnits/unit/UnitValue.java
@@ -42,10 +42,10 @@ public final class UnitValue {
return new UnitValue(
Objects.requireNonNull(unit, "unit must not be null"), value);
}
-
+
private final Unit unit;
private final double value;
-
+
/**
* @param unit the unit being used
* @param value the value being represented
@@ -54,7 +54,7 @@ public final class UnitValue {
this.unit = unit;
this.value = value;
}
-
+
/**
* @return true if this value can be converted to {@code other}.
* @since 2020-10-01
@@ -62,7 +62,7 @@ public final class UnitValue {
public final boolean canConvertTo(Unit other) {
return this.unit.canConvertTo(other);
}
-
+
/**
* @return true if this value can be converted to {@code other}.
* @since 2020-10-01
@@ -70,7 +70,7 @@ public final class UnitValue {
public final <W> boolean canConvertTo(Unitlike<W> other) {
return this.unit.canConvertTo(other);
}
-
+
/**
* Returns a UnitlikeValue that represents the same value expressed in a
* different unitlike form.
@@ -83,7 +83,7 @@ public final class UnitValue {
return UnitlikeValue.of(other,
this.unit.convertTo(other, this.getValue()));
}
-
+
/**
* Returns a UnitValue that represents the same value expressed in a
* different unit
@@ -95,7 +95,7 @@ public final class UnitValue {
return UnitValue.of(other,
this.getUnit().convertTo(other, this.getValue()));
}
-
+
/**
* Returns this unit value represented as a {@code LinearUnitValue} with this
* unit's base unit as the base.
@@ -108,7 +108,7 @@ public final class UnitValue {
final LinearUnit base = LinearUnit.getBase(this.unit).withName(ns);
return this.convertToLinear(base);
}
-
+
/**
* @return a {@code LinearUnitValue} that is equivalent to this value. It
* will have zero uncertainty.
@@ -118,7 +118,7 @@ public final class UnitValue {
return LinearUnitValue.getExact(other,
this.getUnit().convertTo(other, this.getValue()));
}
-
+
/**
* Returns true if this and obj represent the same value, regardless of
* whether or not they are expressed in the same unit. So (1000 m).equals(1
@@ -135,7 +135,7 @@ public final class UnitValue {
.doubleToLongBits(
other.getUnit().convertToBase(other.getValue()));
}
-
+
/**
* @return the unit
* @since 2020-09-29
@@ -143,7 +143,7 @@ public final class UnitValue {
public final Unit getUnit() {
return this.unit;
}
-
+
/**
* @return the value
* @since 2020-09-29
@@ -151,13 +151,13 @@ public final class UnitValue {
public final double getValue() {
return this.value;
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.getUnit().getBase(),
this.getUnit().convertFromBase(this.getValue()));
}
-
+
@Override
public String toString() {
final Optional<String> primaryName = this.getUnit().getPrimaryName();
diff --git a/src/main/java/sevenUnits/unit/Unitlike.java b/src/main/java/sevenUnits/unit/Unitlike.java
index 68de2c2..fef424e 100644
--- a/src/main/java/sevenUnits/unit/Unitlike.java
+++ b/src/main/java/sevenUnits/unit/Unitlike.java
@@ -55,7 +55,7 @@ public abstract class Unitlike<V> implements Nameable {
return new FunctionalUnitlike<>(base, NameSymbol.EMPTY, converterFrom,
converterTo);
}
-
+
/**
* Returns a unitlike form from its base and the functions it uses to convert
* to and from its base.
@@ -78,28 +78,28 @@ public abstract class Unitlike<V> implements Nameable {
final ToDoubleFunction<W> converterTo, final NameSymbol ns) {
return new FunctionalUnitlike<>(base, ns, converterFrom, converterTo);
}
-
+
/**
* The combination of units that this unit is based on.
*
* @since 2019-10-16
*/
private final ObjectProduct<BaseUnit> unitBase;
-
+
/**
* This unit's name(s) and symbol
*
* @since 2020-09-07
*/
private final NameSymbol nameSymbol;
-
+
/**
* Cache storing the result of getDimension()
*
* @since 2019-10-16
*/
private transient ObjectProduct<BaseDimension> dimension = null;
-
+
/**
* @param unitBase
* @since 2020-09-07
@@ -109,7 +109,7 @@ public abstract class Unitlike<V> implements Nameable {
"unitBase may not be null");
this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null");
}
-
+
/**
* Checks if a value expressed in this unitlike form can be converted to a
* value expressed in {@code other}
@@ -124,7 +124,7 @@ public abstract class Unitlike<V> implements Nameable {
Objects.requireNonNull(other, "other must not be null.");
return Objects.equals(this.getBase(), other.getBase());
}
-
+
/**
* Checks if a value expressed in this unitlike form can be converted to a
* value expressed in {@code other}
@@ -139,9 +139,9 @@ public abstract class Unitlike<V> implements Nameable {
Objects.requireNonNull(other, "other must not be null.");
return Objects.equals(this.getBase(), other.getBase());
}
-
+
protected abstract V convertFromBase(double value);
-
+
/**
* Converts a value expressed in this unitlike form to a value expressed in
* {@code other}.
@@ -150,7 +150,7 @@ public abstract class Unitlike<V> implements Nameable {
* {@code other.convertFromBase(this.convertToBase(value))}.
* Therefore, overriding either of those methods will change the
* output of this method.
- *
+ *
* @param other unit to convert to
* @param value value to convert
* @return converted value
@@ -168,7 +168,7 @@ public abstract class Unitlike<V> implements Nameable {
throw new IllegalArgumentException(
String.format("Cannot convert from %s to %s.", this, other));
}
-
+
/**
* Converts a value expressed in this unitlike form to a value expressed in
* {@code other}.
@@ -196,9 +196,9 @@ public abstract class Unitlike<V> implements Nameable {
throw new IllegalArgumentException(
String.format("Cannot convert from %s to %s.", this, other));
}
-
+
protected abstract double convertToBase(V value);
-
+
/**
* @return combination of units that this unit is based on
* @since 2018-12-22
@@ -207,7 +207,7 @@ public abstract class Unitlike<V> implements Nameable {
public final ObjectProduct<BaseUnit> getBase() {
return this.unitBase;
}
-
+
/**
* @return dimension measured by this unit
* @since 2018-12-22
@@ -217,16 +217,16 @@ public abstract class Unitlike<V> implements Nameable {
if (this.dimension == null) {
final Map<BaseUnit, Integer> mapping = this.unitBase.exponentMap();
final Map<BaseDimension, Integer> dimensionMap = new HashMap<>();
-
+
for (final BaseUnit key : mapping.keySet()) {
dimensionMap.put(key.getBaseDimension(), mapping.get(key));
}
-
+
this.dimension = ObjectProduct.fromExponentMapping(dimensionMap);
}
return this.dimension;
}
-
+
/**
* @return the nameSymbol
* @since 2020-09-07
@@ -235,7 +235,7 @@ public abstract class Unitlike<V> implements Nameable {
public final NameSymbol getNameSymbol() {
return this.nameSymbol;
}
-
+
@Override
public String toString() {
return this.getPrimaryName().orElse("Unnamed unitlike form")
@@ -247,7 +247,7 @@ public abstract class Unitlike<V> implements Nameable {
+ (this.getOtherNames().isEmpty() ? ""
: ", also called " + String.join(", ", this.getOtherNames()));
}
-
+
/**
* @param ns name(s) and symbol to use
* @return a copy of this unitlike form with provided name(s) and symbol
diff --git a/src/main/java/sevenUnits/unit/UnitlikeValue.java b/src/main/java/sevenUnits/unit/UnitlikeValue.java
index 26354b1..ad0d1ea 100644
--- a/src/main/java/sevenUnits/unit/UnitlikeValue.java
+++ b/src/main/java/sevenUnits/unit/UnitlikeValue.java
@@ -34,10 +34,10 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
V value) {
return new UnitlikeValue<>(unitlike, value);
}
-
+
private final T unitlike;
private final V value;
-
+
/**
* @param unitlike
* @param value
@@ -47,7 +47,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
this.unitlike = unitlike;
this.value = value;
}
-
+
/**
* @return true if this value can be converted to {@code other}.
* @since 2020-10-01
@@ -55,7 +55,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
public final boolean canConvertTo(Unit other) {
return this.unitlike.canConvertTo(other);
}
-
+
/**
* @return true if this value can be converted to {@code other}.
* @since 2020-10-01
@@ -63,7 +63,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
public final <W> boolean canConvertTo(Unitlike<W> other) {
return this.unitlike.canConvertTo(other);
}
-
+
/**
* Returns a UnitlikeValue that represents the same value expressed in a
* different unitlike form.
@@ -76,7 +76,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
return UnitlikeValue.of(other,
this.unitlike.convertTo(other, this.getValue()));
}
-
+
/**
* Returns a UnitValue that represents the same value expressed in a
* different unit
@@ -88,7 +88,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
return UnitValue.of(other,
this.unitlike.convertTo(other, this.getValue()));
}
-
+
/**
* Returns this unit value represented as a {@code LinearUnitValue} with this
* unit's base unit as the base.
@@ -101,7 +101,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
final LinearUnit base = LinearUnit.getBase(this.unitlike).withName(ns);
return this.convertToLinear(base);
}
-
+
/**
* @return a {@code LinearUnitValue} that is equivalent to this value. It
* will have zero uncertainty.
@@ -111,7 +111,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
return LinearUnitValue.getExact(other,
this.getUnitlike().convertTo(other, this.getValue()));
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -131,7 +131,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
return false;
return true;
}
-
+
/**
* @return the unitlike
* @since 2020-09-29
@@ -139,7 +139,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
public final Unitlike<V> getUnitlike() {
return this.unitlike;
}
-
+
/**
* @return the value
* @since 2020-09-29
@@ -147,7 +147,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
public final V getValue() {
return this.value;
}
-
+
@Override
public int hashCode() {
final int prime = 31;
@@ -158,7 +158,7 @@ final class UnitlikeValue<T extends Unitlike<V>, V> {
+ (this.getValue() == null ? 0 : this.getValue().hashCode());
return result;
}
-
+
@Override
public String toString() {
final Optional<String> primaryName = this.getUnitlike().getPrimaryName();
diff --git a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java
index bee4dd1..b71a4e0 100644
--- a/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java
+++ b/src/main/java/sevenUnits/utils/ConditionalExistenceCollections.java
@@ -67,7 +67,7 @@ public final class ConditionalExistenceCollections {
extends AbstractCollection<E> {
final Collection<E> collection;
final Predicate<E> existenceCondition;
-
+
/**
* Creates the {@code ConditionalExistenceCollection}.
*
@@ -80,38 +80,38 @@ public final class ConditionalExistenceCollections {
this.collection = collection;
this.existenceCondition = existenceCondition;
}
-
+
@Override
public boolean add(final E e) {
return this.collection.add(e) && this.existenceCondition.test(e);
}
-
+
@Override
public void clear() {
this.collection.clear();
}
-
+
@Override
public boolean contains(final Object o) {
if (!this.collection.contains(o))
return false;
-
+
// this collection can only contain instances of E
// since the object is in the collection, we know that it must be an
// instance of E
// therefore this cast will always work
@SuppressWarnings("unchecked")
final E e = (E) o;
-
+
return this.existenceCondition.test(e);
}
-
+
@Override
public Iterator<E> iterator() {
return conditionalExistenceIterator(this.collection.iterator(),
this.existenceCondition);
}
-
+
@Override
public boolean remove(final Object o) {
// remove() must be first in the && statement, otherwise it may not
@@ -119,32 +119,32 @@ public final class ConditionalExistenceCollections {
final boolean containedObject = this.contains(o);
return this.collection.remove(o) && containedObject;
}
-
+
@Override
public int size() {
return (int) this.collection.stream().filter(this.existenceCondition)
.count();
}
-
+
@Override
public Object[] toArray() {
// ensure the toArray operation is supported
this.collection.toArray();
-
+
// if it works, do it for real
return super.toArray();
}
-
+
@Override
public <T> T[] toArray(T[] a) {
// ensure the toArray operation is supported
this.collection.toArray();
-
+
// if it works, do it for real
return super.toArray(a);
}
}
-
+
/**
* Elements in this wrapper iterator only exist if they pass a condition.
*
@@ -157,7 +157,7 @@ public final class ConditionalExistenceCollections {
final Predicate<E> existenceCondition;
E nextElement;
boolean hasNext;
-
+
/**
* Creates the {@code ConditionalExistenceIterator}.
*
@@ -171,7 +171,7 @@ public final class ConditionalExistenceCollections {
this.existenceCondition = condition;
this.getAndSetNextElement();
}
-
+
/**
* Gets the next element, and sets nextElement and hasNext accordingly.
*
@@ -188,12 +188,12 @@ public final class ConditionalExistenceCollections {
} while (!this.existenceCondition.test(this.nextElement));
this.hasNext = true;
}
-
+
@Override
public boolean hasNext() {
return this.hasNext;
}
-
+
@Override
public E next() {
if (this.hasNext()) {
@@ -203,13 +203,13 @@ public final class ConditionalExistenceCollections {
} else
throw new NoSuchElementException();
}
-
+
@Override
public void remove() {
this.iterator.remove();
}
}
-
+
/**
* Mappings in this map only exist if the entry passes some condition.
*
@@ -221,7 +221,7 @@ public final class ConditionalExistenceCollections {
static final class ConditionalExistenceMap<K, V> extends AbstractMap<K, V> {
Map<K, V> map;
Predicate<Entry<K, V>> entryExistenceCondition;
-
+
/**
* Creates the {@code ConditionalExistenceMap}.
*
@@ -234,81 +234,81 @@ public final class ConditionalExistenceCollections {
this.map = map;
this.entryExistenceCondition = entryExistenceCondition;
}
-
+
@Override
public boolean containsKey(final Object key) {
if (!this.map.containsKey(key))
return false;
-
+
// only instances of K have mappings in the backing map
// since we know that key is a valid key, it must be an instance of K
@SuppressWarnings("unchecked")
final K keyAsK = (K) key;
-
+
// get and test entry
final V value = this.map.get(key);
final Entry<K, V> entry = new SimpleEntry<>(keyAsK, value);
return this.entryExistenceCondition.test(entry);
}
-
+
@Override
public Set<Entry<K, V>> entrySet() {
return conditionalExistenceSet(this.map.entrySet(),
this.entryExistenceCondition);
}
-
+
@Override
public V get(final Object key) {
return this.containsKey(key) ? this.map.get(key) : null;
}
-
+
private final Entry<K, V> getEntry(K key) {
return new Entry<>() {
@Override
public K getKey() {
return key;
}
-
+
@Override
public V getValue() {
return ConditionalExistenceMap.this.map.get(key);
}
-
+
@Override
public V setValue(V value) {
return ConditionalExistenceMap.this.map.put(key, value);
}
};
}
-
+
@Override
public Set<K> keySet() {
return conditionalExistenceSet(this.map.keySet(),
k -> this.entryExistenceCondition.test(this.getEntry(k)));
}
-
+
@Override
public V put(final K key, final V value) {
final V oldValue = this.map.put(key, value);
-
+
// get and test entry
final Entry<K, V> entry = new SimpleEntry<>(key, oldValue);
return this.entryExistenceCondition.test(entry) ? oldValue : null;
}
-
+
@Override
public V remove(final Object key) {
final V oldValue = this.map.remove(key);
return this.containsKey(key) ? oldValue : null;
}
-
+
@Override
public Collection<V> values() {
// maybe change this to use ConditionalExistenceCollection
return super.values();
}
}
-
+
/**
* Elements in this set only exist if a certain condition is true.
*
@@ -319,7 +319,7 @@ public final class ConditionalExistenceCollections {
static final class ConditionalExistenceSet<E> extends AbstractSet<E> {
private final Set<E> set;
private final Predicate<E> existenceCondition;
-
+
/**
* Creates the {@code ConditionalNonexistenceSet}.
*
@@ -332,7 +332,7 @@ public final class ConditionalExistenceCollections {
this.set = set;
this.existenceCondition = existenceCondition;
}
-
+
/**
* {@inheritDoc}
* <p>
@@ -343,33 +343,33 @@ public final class ConditionalExistenceCollections {
public boolean add(final E e) {
return this.set.add(e) && this.existenceCondition.test(e);
}
-
+
@Override
public void clear() {
this.set.clear();
}
-
+
@Override
public boolean contains(final Object o) {
if (!this.set.contains(o))
return false;
-
+
// this set can only contain instances of E
// since the object is in the set, we know that it must be an instance
// of E
// therefore this cast will always work
@SuppressWarnings("unchecked")
final E e = (E) o;
-
+
return this.existenceCondition.test(e);
}
-
+
@Override
public Iterator<E> iterator() {
return conditionalExistenceIterator(this.set.iterator(),
this.existenceCondition);
}
-
+
@Override
public boolean remove(final Object o) {
// remove() must be first in the && statement, otherwise it may not
@@ -377,31 +377,31 @@ public final class ConditionalExistenceCollections {
final boolean containedObject = this.contains(o);
return this.set.remove(o) && containedObject;
}
-
+
@Override
public int size() {
return (int) this.set.stream().filter(this.existenceCondition).count();
}
-
+
@Override
public Object[] toArray() {
// ensure the toArray operation is supported
this.set.toArray();
-
+
// if it works, do it for real
return super.toArray();
}
-
+
@Override
public <T> T[] toArray(T[] a) {
// ensure the toArray operation is supported
this.set.toArray();
-
+
// if it works, do it for real
return super.toArray(a);
}
}
-
+
/**
* Elements in the returned wrapper collection are ignored if they don't pass
* a condition.
@@ -418,7 +418,7 @@ public final class ConditionalExistenceCollections {
return new ConditionalExistenceCollection<>(collection,
existenceCondition);
}
-
+
/**
* Elements in the returned wrapper iterator are ignored if they don't pass a
* condition.
@@ -433,7 +433,7 @@ public final class ConditionalExistenceCollections {
final Iterator<E> iterator, final Predicate<E> existenceCondition) {
return new ConditionalExistenceIterator<>(iterator, existenceCondition);
}
-
+
/**
* Mappings in the returned wrapper map are ignored if the corresponding
* entry doesn't pass a condition
@@ -450,7 +450,7 @@ public final class ConditionalExistenceCollections {
final Predicate<Entry<K, V>> entryExistenceCondition) {
return new ConditionalExistenceMap<>(map, entryExistenceCondition);
}
-
+
/**
* Elements in the returned wrapper set are ignored if they don't pass a
* condition.
diff --git a/src/main/java/sevenUnits/utils/DecimalComparison.java b/src/main/java/sevenUnits/utils/DecimalComparison.java
index a5cbbaa..0515b6b 100644
--- a/src/main/java/sevenUnits/utils/DecimalComparison.java
+++ b/src/main/java/sevenUnits/utils/DecimalComparison.java
@@ -34,7 +34,7 @@ public final class DecimalComparison {
* @since v0.2.0
*/
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.
@@ -43,7 +43,7 @@ public final class DecimalComparison {
* @since v0.2.0
*/
public static final float FLOAT_EPSILON = 1.0e-6f;
-
+
/**
* Tests for equality of double values using {@link #DOUBLE_EPSILON}.
* <p>
@@ -74,7 +74,7 @@ public final class DecimalComparison {
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.
*
@@ -106,7 +106,7 @@ public final class DecimalComparison {
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}.
*
@@ -136,7 +136,7 @@ public final class DecimalComparison {
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.
*
@@ -168,7 +168,7 @@ public final class DecimalComparison {
final float epsilon) {
return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b));
}
-
+
/**
* Tests for equality of {@code UncertainDouble} values using
* {@link #DOUBLE_EPSILON}.
@@ -201,7 +201,7 @@ public final class DecimalComparison {
return DecimalComparison.equals(a.value(), b.value())
&& DecimalComparison.equals(a.uncertainty(), b.uncertainty());
}
-
+
/**
* Tests for {@code UncertainDouble} equality using a custom epsilon value.
*
@@ -235,7 +235,7 @@ public final class DecimalComparison {
&& DecimalComparison.equals(a.uncertainty(), b.uncertainty(),
epsilon);
}
-
+
/**
* Takes the hash code of doubles. Values that are equal according to
* {@link #equals(double, double)} will have the same hash code.
@@ -247,10 +247,10 @@ public final class DecimalComparison {
public static final int hash(final double d) {
return Float.hashCode((float) d);
}
-
+
// You may NOT get any DecimalComparison instances
private DecimalComparison() {
throw new AssertionError();
}
-
+
}
diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java
index 941c2a4..e248ff0 100644
--- a/src/main/java/sevenUnits/utils/ExpressionParser.java
+++ b/src/main/java/sevenUnits/utils/ExpressionParser.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2019 Adrien Hopkins
+ * Copyright (C) 2019, 2024 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
@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.UnaryOperator;
@@ -55,7 +56,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final Function<String, ? extends T> objectObtainer;
-
+
/**
* The function of the space as an operator (like 3 x y)
*
@@ -63,7 +64,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private String spaceFunction = null;
-
+
/**
* A map mapping operator strings to operator functions, for unary
* operators.
@@ -72,7 +73,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final Map<String, PriorityUnaryOperator<T>> unaryOperators;
-
+
/**
* A map mapping operator strings to operator functions, for binary
* operators.
@@ -81,7 +82,14 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final Map<String, PriorityBinaryOperator<T>> binaryOperators;
-
+
+ /**
+ * A map mapping operator strings to numeric functions.
+ *
+ * @since 2024-03-23
+ */
+ private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators;
+
/**
* Creates the {@code Builder}.
*
@@ -96,8 +104,9 @@ public final class ExpressionParser<T> {
"objectObtainer must not be null.");
this.unaryOperators = new HashMap<>();
this.binaryOperators = new HashMap<>();
+ this.numericOperators = new HashMap<>();
}
-
+
/**
* Adds a binary operator to the builder.
*
@@ -115,7 +124,7 @@ public final class ExpressionParser<T> {
final BinaryOperator<T> 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<T> priorityOperator = new PriorityBinaryOperator<>(
@@ -124,12 +133,42 @@ public final class ExpressionParser<T> {
public T apply(final T t, final T u) {
return operator.apply(t, u);
}
-
+
};
this.binaryOperators.put(text, priorityOperator);
return this;
}
-
+
+ /**
+ * Adds a two-argument operator where the second operator is a number.
+ * This is used for operations like vector scaling and exponentation.
+ *
+ * @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
+ */
+ public Builder<T> addNumericOperator(final String text,
+ final BiFunction<T, UncertainDouble, T> 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 PriorityBiFunction<T, UncertainDouble, T> priorityOperator = new PriorityBiFunction<>(
+ priority) {
+ @Override
+ public T apply(final T t, final UncertainDouble u) {
+ return operator.apply(t, u);
+ }
+
+ };
+ this.numericOperators.put(text, priorityOperator);
+ return this;
+ }
+
/**
* Adds a function for spaces. You must use the text of an existing binary
* operator.
@@ -141,15 +180,15 @@ public final class ExpressionParser<T> {
*/
public Builder<T> 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.
*
@@ -167,7 +206,7 @@ public final class ExpressionParser<T> {
final UnaryOperator<T> 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<T> priorityOperator = new PriorityUnaryOperator<>(
@@ -180,7 +219,7 @@ public final class ExpressionParser<T> {
this.unaryOperators.put(text, priorityOperator);
return this;
}
-
+
/**
* @return an {@code ExpressionParser<T>} instance with the properties
* given to this builder
@@ -189,10 +228,10 @@ public final class ExpressionParser<T> {
*/
public ExpressionParser<T> build() {
return new ExpressionParser<>(this.objectObtainer, this.unaryOperators,
- this.binaryOperators, this.spaceFunction);
+ this.binaryOperators, this.numericOperators, this.spaceFunction);
}
}
-
+
/**
* A binary operator with a priority field that determines which operators
* apply first.
@@ -212,7 +251,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final int priority;
-
+
/**
* Creates the {@code PriorityBinaryOperator}.
*
@@ -223,7 +262,7 @@ public final class ExpressionParser<T> {
public PriorityBinaryOperator(final int priority) {
this.priority = priority;
}
-
+
/**
* Compares this object to another by priority.
*
@@ -243,7 +282,68 @@ public final class ExpressionParser<T> {
else
return 0;
}
-
+
+ /**
+ * @return priority
+ * @since 2019-03-22
+ * @since v0.2.0
+ */
+ public final int getPriority() {
+ return this.priority;
+ }
+ }
+
+ /**
+ * A binary operator with a priority field that determines which operators
+ * apply first.
+ *
+ * @author Adrien Hopkins
+ * @param <T> type of operand and result
+ * @since 2019-03-17
+ * @since v0.2.0
+ */
+ private static abstract class PriorityBiFunction<T, U, R> implements
+ BiFunction<T, U, R>, Comparable<PriorityBiFunction<T, U, R>> {
+ /**
+ * 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;
+
+ /**
+ * Creates the {@code PriorityBinaryOperator}.
+ *
+ * @param priority operator's priority
+ * @since 2019-03-17
+ * @since v0.2.0
+ */
+ public PriorityBiFunction(final int priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * Compares this object to another by priority.
+ *
+ * <p>
+ * {@inheritDoc}
+ * </p>
+ *
+ * @since 2019-03-17
+ * @since v0.2.0
+ */
+ @Override
+ public int compareTo(final PriorityBiFunction<T, U, R> 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
@@ -253,7 +353,7 @@ public final class ExpressionParser<T> {
return this.priority;
}
}
-
+
/**
* A unary operator with a priority field that determines which operators
* apply first.
@@ -273,7 +373,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final int priority;
-
+
/**
* Creates the {@code PriorityUnaryOperator}.
*
@@ -284,7 +384,7 @@ public final class ExpressionParser<T> {
public PriorityUnaryOperator(final int priority) {
this.priority = priority;
}
-
+
/**
* Compares this object to another by priority.
*
@@ -304,7 +404,7 @@ public final class ExpressionParser<T> {
else
return 0;
}
-
+
/**
* @return priority
* @since 2019-03-22
@@ -314,7 +414,7 @@ public final class ExpressionParser<T> {
return this.priority;
}
}
-
+
/**
* The types of tokens that are available.
*
@@ -323,9 +423,9 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private static enum TokenType {
- OBJECT, UNARY_OPERATOR, BINARY_OPERATOR;
+ OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR;
}
-
+
/**
* The opening bracket.
*
@@ -333,7 +433,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
public static final char OPENING_BRACKET = '(';
-
+
/**
* The closing bracket.
*
@@ -341,7 +441,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
public static final char CLOSING_BRACKET = ')';
-
+
/**
* Finds the other bracket in a pair of brackets, given the position of one.
*
@@ -355,9 +455,9 @@ public final class ExpressionParser<T> {
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) {
@@ -374,16 +474,16 @@ public final class ExpressionParser<T> {
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) {
@@ -392,10 +492,10 @@ public final class ExpressionParser<T> {
return currentPosition;
}
}
-
+
throw new IllegalArgumentException("No matching bracket found.");
}
-
+
/**
* A function that obtains a parseable object from a string. For example, an
* integer {@code ExpressionParser} would use {@code Integer::parseInt}.
@@ -404,7 +504,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final Function<String, ? extends T> objectObtainer;
-
+
/**
* A map mapping operator strings to operator functions, for unary operators.
*
@@ -412,7 +512,7 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final Map<String, PriorityUnaryOperator<T>> unaryOperators;
-
+
/**
* A map mapping operator strings to operator functions, for binary
* operators.
@@ -421,7 +521,14 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final Map<String, PriorityBinaryOperator<T>> binaryOperators;
-
+
+ /**
+ * A map mapping operator strings to numeric functions.
+ *
+ * @since 2024-03-23
+ */
+ private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators;
+
/**
* The operator for space, or null if spaces have no function.
*
@@ -429,27 +536,30 @@ public final class ExpressionParser<T> {
* @since v0.2.0
*/
private final String spaceOperator;
-
+
/**
* Creates the {@code ExpressionParser}.
*
- * @param objectObtainer function to get objects from strings
- * @param unaryOperators unary operators available to the parser
- * @param binaryOperators binary operators available to the parser
- * @param spaceOperator operator used by spaces
+ * @param objectObtainer function to get objects from strings
+ * @param unaryOperators unary operators available to the parser
+ * @param binaryOperators binary operators available to the parser
+ * @param numericOperators numeric operators available to the parser
+ * @param spaceOperator operator used by spaces
* @since 2019-03-14
* @since v0.2.0
*/
private ExpressionParser(final Function<String, ? extends T> objectObtainer,
final Map<String, PriorityUnaryOperator<T>> unaryOperators,
final Map<String, PriorityBinaryOperator<T>> binaryOperators,
+ final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators,
final String spaceOperator) {
this.objectObtainer = objectObtainer;
this.unaryOperators = unaryOperators;
this.binaryOperators = binaryOperators;
+ this.numericOperators = numericOperators;
this.spaceOperator = spaceOperator;
}
-
+
/**
* Converts a given mathematical expression to reverse Polish notation
* (operators after operands).
@@ -468,19 +578,19 @@ public final class ExpressionParser<T> {
*/
String convertExpressionToReversePolish(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
final List<String> 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);
-
+
// check for function
if (openingBracketPosition > 0
&& partialExpression.charAt(openingBracketPosition - 1) != ' ') {
@@ -510,15 +620,15 @@ public final class ExpressionParser<T> {
.substring(closingBracketPosition + 1);
}
}
-
+
// add everything else
components.addAll(Arrays.asList(partialExpression.split(" ")));
-
+
// remove empty entries
while (components.contains("")) {
components.remove("");
}
-
+
// deal with space multiplication (x y)
if (this.spaceOperator != null) {
for (int i = 0; i < components.size() - 1; i++) {
@@ -528,7 +638,7 @@ public final class ExpressionParser<T> {
}
}
}
-
+
// turn the expression into reverse Polish
while (true) {
final int highestPriorityOperatorPosition = this
@@ -536,7 +646,7 @@ public final class ExpressionParser<T> {
if (highestPriorityOperatorPosition == -1) {
break;
}
-
+
// swap components based on what kind of operator there is
// 1 + 2 becomes 2 1 +
// - 1 becomes 1 -
@@ -554,6 +664,7 @@ public final class ExpressionParser<T> {
operand + " " + unaryOperator);
break;
case BINARY_OPERATOR:
+ case NUMERIC_OPERATOR:
if (components.size() < 3)
throw new IllegalArgumentException(
"Invalid expression \"" + expression + "\"");
@@ -570,11 +681,11 @@ public final class ExpressionParser<T> {
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);
}
@@ -583,7 +694,7 @@ public final class ExpressionParser<T> {
}
return expressionRPN;
}
-
+
/**
* Finds the position of the highest-priority operator in a list
*
@@ -601,18 +712,18 @@ public final class ExpressionParser<T> {
// 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<T> unaryOperator = this.unaryOperators
.get(components.get(i));
final int unaryPriority = unaryOperator.getPriority();
-
+
if (unaryPriority > maxPriority) {
maxPriority = unaryPriority;
maxPriorityPosition = i;
@@ -622,21 +733,31 @@ public final class ExpressionParser<T> {
final PriorityBinaryOperator<T> binaryOperator = this.binaryOperators
.get(components.get(i));
final int binaryPriority = binaryOperator.getPriority();
-
+
if (binaryPriority > maxPriority) {
maxPriority = binaryPriority;
maxPriorityPosition = i;
}
break;
+ case NUMERIC_OPERATOR:
+ final PriorityBiFunction<T, UncertainDouble, T> numericOperator = this.numericOperators
+ .get(components.get(i));
+ final int numericPriority = numericOperator.getPriority();
+
+ if (numericPriority > maxPriority) {
+ maxPriority = numericPriority;
+ maxPriorityPosition = i;
+ }
+ break;
default:
break;
}
}
-
+
// max priority position found
return maxPriorityPosition;
}
-
+
/**
* Determines whether an inputted string is an object or an operator
*
@@ -648,15 +769,17 @@ public final class ExpressionParser<T> {
*/
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 if (this.numericOperators.containsKey(token))
+ return TokenType.NUMERIC_OPERATOR;
else
return TokenType.OBJECT;
}
-
+
/**
* Parses an expression.
*
@@ -670,7 +793,7 @@ public final class ExpressionParser<T> {
return this.parseReversePolishExpression(
this.convertExpressionToReversePolish(expression));
}
-
+
/**
* Parses an expression expressed in reverse Polish notation.
*
@@ -682,55 +805,86 @@ public final class ExpressionParser<T> {
*/
T parseReversePolishExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
-
+
final Deque<T> stack = new ArrayDeque<>();
-
+ final Deque<UncertainDouble> doubleStack = 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<T> binaryOperator = this.binaryOperators
.get(item);
-
+
stack.push(binaryOperator.apply(o1, o2));
break;
-
+
+ case NUMERIC_OPERATOR:
+ if (stack.size() < 1 || doubleStack.size() < 1)
+ throw new IllegalStateException(String.format(
+ "Attempted to call binary operator %s with insufficient arguments.",
+ item));
+
+ final T ot = stack.pop();
+ final UncertainDouble on = doubleStack.pop();
+ final BiFunction<T, UncertainDouble, T> op = this.numericOperators
+ .get(item);
+ stack.push(op.apply(ot, on));
+ break;
+
case OBJECT:
// just add it to the stack
- stack.push(this.objectObtainer.apply(item));
+ // these try-catch statements are necessary
+ // to make the code as generalizable as possible
+ // also they're required for number formatting code because
+ // that's the only way to tell if an expression is a number or not.
+ try {
+ stack.push(this.objectObtainer.apply(item));
+ } catch (Exception e) {
+ try {
+ doubleStack.push(UncertainDouble.fromString(item));
+ } catch (IllegalArgumentException e2) {
+ try {
+ doubleStack.push(
+ UncertainDouble.of(Double.parseDouble(item), 0));
+ } catch (NumberFormatException e3) {
+ throw e;
+ }
+ }
+ }
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<T> 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(
diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java
index 9388f63..49c44fa 100644
--- a/src/main/java/sevenUnits/utils/NameSymbol.java
+++ b/src/main/java/sevenUnits/utils/NameSymbol.java
@@ -33,7 +33,7 @@ import java.util.Set;
public final class NameSymbol {
public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(),
Optional.empty(), new HashSet<>());
-
+
/**
* Creates a {@code NameSymbol}, ensuring that if primaryName is null and
* otherNames is not empty, one name is moved from otherNames to primaryName
@@ -43,7 +43,7 @@ public final class NameSymbol {
private static final NameSymbol create(final String name,
final String symbol, final Set<String> otherNames) {
final Optional<String> primaryName;
-
+
if (name == null && !otherNames.isEmpty()) {
// get primary name and remove it from savedNames
final Iterator<String> it = otherNames.iterator();
@@ -53,11 +53,11 @@ public final class NameSymbol {
} else {
primaryName = Optional.ofNullable(name);
}
-
+
return new NameSymbol(primaryName, Optional.ofNullable(symbol),
otherNames);
}
-
+
/**
* Gets a {@code NameSymbol} with a primary name, a symbol and no other
* names.
@@ -72,7 +72,7 @@ public final class NameSymbol {
return new NameSymbol(Optional.of(name), Optional.of(symbol),
new HashSet<>());
}
-
+
/**
* Gets a {@code NameSymbol} with a primary name, a symbol and additional
* names.
@@ -90,7 +90,7 @@ public final class NameSymbol {
new HashSet<>(Objects.requireNonNull(otherNames,
"otherNames must not be null.")));
}
-
+
/**
* h * Gets a {@code NameSymbol} with a primary name, a symbol and additional
* names.
@@ -108,7 +108,7 @@ public final class NameSymbol {
new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames,
"otherNames must not be null."))));
}
-
+
/**
* Gets a {@code NameSymbol} with a primary name, no symbol, and no other
* names.
@@ -122,7 +122,7 @@ public final class NameSymbol {
return new NameSymbol(Optional.of(name), Optional.empty(),
new HashSet<>());
}
-
+
/**
* Gets a {@code NameSymbol} with a primary name, a symbol and additional
* names.
@@ -145,7 +145,7 @@ public final class NameSymbol {
return NameSymbol.create(name, symbol,
otherNames == null ? new HashSet<>() : new HashSet<>(otherNames));
}
-
+
/**
* h * Gets a {@code NameSymbol} with a primary name, a symbol and additional
* names.
@@ -168,7 +168,7 @@ public final class NameSymbol {
return create(name, symbol, otherNames == null ? new HashSet<>()
: new HashSet<>(Arrays.asList(otherNames)));
}
-
+
/**
* Gets a {@code NameSymbol} with a symbol and no names.
*
@@ -181,12 +181,12 @@ public final class NameSymbol {
return new NameSymbol(Optional.empty(), Optional.of(symbol),
new HashSet<>());
}
-
+
private final Optional<String> primaryName;
private final Optional<String> symbol;
-
+
private final Set<String> otherNames;
-
+
/**
* Creates the {@code NameSymbol}.
*
@@ -202,12 +202,12 @@ public final class NameSymbol {
this.symbol = symbol;
otherNames.remove(null);
this.otherNames = Collections.unmodifiableSet(otherNames);
-
+
if (this.primaryName.isEmpty()) {
assert this.otherNames.isEmpty();
}
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -232,7 +232,7 @@ public final class NameSymbol {
return false;
return true;
}
-
+
/**
* @return otherNames
* @since 2019-10-21
@@ -240,7 +240,7 @@ public final class NameSymbol {
public final Set<String> getOtherNames() {
return this.otherNames;
}
-
+
/**
* @return primaryName
* @since 2019-10-21
@@ -248,7 +248,7 @@ public final class NameSymbol {
public final Optional<String> getPrimaryName() {
return this.primaryName;
}
-
+
/**
* @return symbol
* @since 2019-10-21
@@ -256,7 +256,7 @@ public final class NameSymbol {
public final Optional<String> getSymbol() {
return this.symbol;
}
-
+
@Override
public int hashCode() {
final int prime = 31;
@@ -269,7 +269,7 @@ public final class NameSymbol {
+ (this.symbol == null ? 0 : this.symbol.hashCode());
return result;
}
-
+
/**
* @return true iff this {@code NameSymbol} contains no names or symbols.
*/
@@ -277,7 +277,7 @@ public final class NameSymbol {
// if primaryName is empty, otherNames must also be empty
return this.primaryName.isEmpty() && this.symbol.isEmpty();
}
-
+
@Override
public String toString() {
if (this.isEmpty())
@@ -288,7 +288,7 @@ public final class NameSymbol {
else
return this.primaryName.orElseGet(this.symbol::orElseThrow);
}
-
+
/**
* Creates and returns a copy of this {@code NameSymbol} with the provided
* extra name. If this {@code NameSymbol} has a primary name, the provided
diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java
index e469d04..3959a64 100644
--- a/src/main/java/sevenUnits/utils/Nameable.java
+++ b/src/main/java/sevenUnits/utils/Nameable.java
@@ -35,14 +35,14 @@ public interface Nameable {
final NameSymbol ns = this.getNameSymbol();
return ns.getPrimaryName().or(ns::getSymbol).orElse("Unnamed");
}
-
+
/**
* @return a {@code NameSymbol} that contains this object's primary name,
* symbol and other names
* @since 2020-09-07
*/
NameSymbol getNameSymbol();
-
+
/**
* @return set of alternate names
* @since 2020-09-07
@@ -50,7 +50,7 @@ public interface Nameable {
default Set<String> getOtherNames() {
return this.getNameSymbol().getOtherNames();
}
-
+
/**
* @return preferred name of object
* @since 2020-09-07
@@ -58,7 +58,7 @@ public interface Nameable {
default Optional<String> getPrimaryName() {
return this.getNameSymbol().getPrimaryName();
}
-
+
/**
* @return a short name for the object - if there's a symbol, it's that,
* otherwise the symbol, otherwise "Unnamed"
@@ -68,7 +68,7 @@ public interface Nameable {
final NameSymbol ns = this.getNameSymbol();
return ns.getSymbol().or(ns::getPrimaryName).orElse("Unnamed");
}
-
+
/**
* @return short symbol representing object
* @since 2020-09-07
diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java
index 66bb773..5a29d79 100644
--- a/src/main/java/sevenUnits/utils/ObjectProduct.java
+++ b/src/main/java/sevenUnits/utils/ObjectProduct.java
@@ -44,7 +44,7 @@ public class ObjectProduct<T> implements Nameable {
public static final <T> ObjectProduct<T> empty() {
return new ObjectProduct<>(new HashMap<>());
}
-
+
/**
* Gets an {@code ObjectProduct} from an object-to-integer mapping
*
@@ -57,7 +57,7 @@ public class ObjectProduct<T> implements Nameable {
final Map<T, Integer> map) {
return new ObjectProduct<>(new HashMap<>(map));
}
-
+
/**
* Gets an ObjectProduct that has one of the inputted argument, and nothing
* else.
@@ -73,7 +73,7 @@ public class ObjectProduct<T> implements Nameable {
map.put(object, 1);
return new ObjectProduct<>(map);
}
-
+
/**
* The objects that make up the product, mapped to their exponents. This map
* treats zero as null, and is immutable.
@@ -81,12 +81,12 @@ public class ObjectProduct<T> implements Nameable {
* @since 2019-10-16
*/
final Map<T, Integer> exponents;
-
+
/**
* The object's name and symbol
*/
private final NameSymbol nameSymbol;
-
+
/**
* Creates a {@code ObjectProduct} without a name/symbol.
*
@@ -96,7 +96,7 @@ public class ObjectProduct<T> implements Nameable {
ObjectProduct(final Map<T, Integer> exponents) {
this(exponents, NameSymbol.EMPTY);
}
-
+
/**
* Creates the {@code ObjectProduct}.
*
@@ -110,7 +110,7 @@ public class ObjectProduct<T> implements Nameable {
e -> !Integer.valueOf(0).equals(e.getValue())));
this.nameSymbol = nameSymbol;
}
-
+
/**
* Calculates the quotient of two products
*
@@ -125,17 +125,17 @@ public class ObjectProduct<T> implements Nameable {
final Set<T> objects = new HashSet<>();
objects.addAll(this.getBaseSet());
objects.addAll(other.getBaseSet());
-
+
// get a list of all exponents
final Map<T, Integer> map = new HashMap<>(objects.size());
for (final T key : objects) {
map.put(key, this.getExponent(key) - other.getExponent(key));
}
-
+
// create the product
return new ObjectProduct<>(map);
}
-
+
// this method relies on the use of ZeroIsNullMap
@Override
public boolean equals(final Object obj) {
@@ -146,7 +146,7 @@ public class ObjectProduct<T> implements Nameable {
final ObjectProduct<?> other = (ObjectProduct<?>) obj;
return Objects.equals(this.exponents, other.exponents);
}
-
+
/**
* @return immutable map mapping objects to exponents
* @since 2019-10-16
@@ -154,7 +154,7 @@ public class ObjectProduct<T> implements Nameable {
public Map<T, Integer> exponentMap() {
return this.exponents;
}
-
+
/**
* @return a set of all of the base objects with non-zero exponents that make
* up this dimension.
@@ -163,7 +163,7 @@ public class ObjectProduct<T> implements Nameable {
*/
public final Set<T> getBaseSet() {
final Set<T> dimensions = new HashSet<>();
-
+
// add all dimensions with a nonzero exponent - zero exponents shouldn't
// be there in the first place
for (final T dimension : this.exponents.keySet()) {
@@ -171,10 +171,10 @@ public class ObjectProduct<T> implements Nameable {
dimensions.add(dimension);
}
}
-
+
return dimensions;
}
-
+
/**
* Gets the exponent for a specific dimension.
*
@@ -186,17 +186,17 @@ public class ObjectProduct<T> implements Nameable {
public int getExponent(final T dimension) {
return this.exponents.getOrDefault(dimension, 0);
}
-
+
@Override
public NameSymbol getNameSymbol() {
return this.nameSymbol;
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.exponents);
}
-
+
/**
* @return true if this product is a single object, i.e. it has one exponent
* of one and no other nonzero exponents
@@ -214,7 +214,7 @@ public class ObjectProduct<T> implements Nameable {
}
return oneCount == 1 && !twoOrMore;
}
-
+
/**
* Multiplies this product by another
*
@@ -229,17 +229,17 @@ public class ObjectProduct<T> implements Nameable {
final Set<T> objects = new HashSet<>();
objects.addAll(this.getBaseSet());
objects.addAll(other.getBaseSet());
-
+
// get a list of all exponents
final Map<T, Integer> map = new HashMap<>(objects.size());
for (final T key : objects) {
map.put(key, this.getExponent(key) + other.getExponent(key));
}
-
+
// create the product
return new ObjectProduct<>(map);
}
-
+
/**
* Returns this product, but to an exponent
*
@@ -254,7 +254,7 @@ public class ObjectProduct<T> implements Nameable {
}
return new ObjectProduct<>(map);
}
-
+
/**
* Converts this product to a string using the objects'
* {@link Object#toString()} method (or {@link Nameable#getShortName} if
@@ -271,7 +271,7 @@ public class ObjectProduct<T> implements Nameable {
.toString(o -> o instanceof Nameable ? ((Nameable) o).getShortName()
: o.toString());
}
-
+
/**
* Converts this product to a string. The objects that make up this product
* are represented by {@code objectToString}
@@ -283,7 +283,7 @@ public class ObjectProduct<T> implements Nameable {
public String toString(final Function<T, String> objectToString) {
final List<String> positiveStringComponents = new ArrayList<>();
final List<String> negativeStringComponents = new ArrayList<>();
-
+
// for each base object that makes up this object, add it and its exponent
for (final T object : this.getBaseSet()) {
final int exponent = this.exponents.get(object);
@@ -297,15 +297,15 @@ public class ObjectProduct<T> implements Nameable {
objectToString.apply(object), -exponent));
}
}
-
+
final String positiveString = positiveStringComponents.isEmpty() ? "1"
: String.join(" * ", positiveStringComponents);
final String negativeString = negativeStringComponents.isEmpty() ? ""
: " / " + String.join(" * ", negativeStringComponents);
-
+
return positiveString + negativeString;
}
-
+
/**
* @return named version of this {@code ObjectProduct}, using data from
* {@code nameSymbol}
diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java
index e80e16e..fc47baa 100644
--- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java
+++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java
@@ -61,7 +61,7 @@ public final class SemanticVersionNumber
private final int patch;
private final List<String> preReleaseIdentifiers;
private final List<String> buildMetadata;
-
+
/**
* Creates a builder which can be used to create a
* {@code SemanticVersionNumber}
@@ -79,7 +79,7 @@ public final class SemanticVersionNumber
this.preReleaseIdentifiers = new ArrayList<>();
this.buildMetadata = new ArrayList<>();
}
-
+
/**
* @return version number created by this builder
* @since v0.4.0
@@ -89,7 +89,7 @@ public final class SemanticVersionNumber
return new SemanticVersionNumber(this.major, this.minor, this.patch,
this.preReleaseIdentifiers, this.buildMetadata);
}
-
+
/**
* Adds one or more build metadata identifiers
*
@@ -109,7 +109,7 @@ public final class SemanticVersionNumber
}
return this;
}
-
+
/**
* Adds one or more build metadata identifiers
*
@@ -129,7 +129,7 @@ public final class SemanticVersionNumber
}
return this;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -142,13 +142,13 @@ public final class SemanticVersionNumber
&& this.patch == other.patch && Objects.equals(
this.preReleaseIdentifiers, other.preReleaseIdentifiers);
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.buildMetadata, this.major, this.minor,
this.patch, this.preReleaseIdentifiers);
}
-
+
/**
* Adds one or more numeric identifiers to the version number
*
@@ -167,7 +167,7 @@ public final class SemanticVersionNumber
}
return this;
}
-
+
/**
* Adds one or more pre-release identifier(s) to the version number
*
@@ -187,7 +187,7 @@ public final class SemanticVersionNumber
}
return this;
}
-
+
/**
* Adds one or more pre-release identifier(s) to the version number
*
@@ -207,7 +207,7 @@ public final class SemanticVersionNumber
}
return this;
}
-
+
/**
* Adds a string identifier and an integer identifer to pre-release data
*
@@ -229,13 +229,13 @@ public final class SemanticVersionNumber
this.preReleaseIdentifiers.add(Integer.toString(identifier2));
return this;
}
-
+
@Override
public String toString() {
return "Semantic Version Builder: " + this.build().toString();
}
}
-
+
/**
* An alternative comparison method for version numbers. This uses the
* version's natural order, but the build metadata will be compared (using
@@ -257,21 +257,21 @@ public final class SemanticVersionNumber
return naturalComparison;
};
};
-
+
/** The alphanumeric pattern all identifiers must follow */
private static final Pattern VALID_IDENTIFIER = Pattern
.compile("[0-9A-Za-z-]+");
-
+
/** The numeric pattern which causes special behaviour */
private static final Pattern NUMERIC_IDENTIFER = Pattern.compile("[0-9]+");
-
+
/** The pattern for a version number */
private static final Pattern VERSION_NUMBER = Pattern
.compile("(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)" // main
// version
+ "(?:-([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?" // pre-release
+ "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"); // build data
-
+
/**
* Creates a builder that can be used to create a version number
*
@@ -296,7 +296,7 @@ public final class SemanticVersionNumber
"Patch version must be non-negative.");
return new SemanticVersionNumber.Builder(major, minor, patch);
}
-
+
/**
* Compares two lists of strings based on SemVer's precedence rules
*
@@ -311,24 +311,24 @@ public final class SemanticVersionNumber
// test pre-release size
final int aSize = a.size();
final int bSize = b.size();
-
+
// no identifiers is greater than any identifiers
if (aSize != 0 && bSize == 0)
return -1;
else if (aSize == 0 && bSize != 0)
return 1;
-
+
// test identifiers one by one
for (int i = 0; i < Math.min(aSize, bSize); i++) {
final String aElement = a.get(i);
final String bElement = b.get(i);
-
+
if (NUMERIC_IDENTIFER.matcher(aElement).matches()) {
if (NUMERIC_IDENTIFER.matcher(bElement).matches()) {
// both are numbers, compare them
final int aNumber = Integer.parseInt(aElement);
final int bNumber = Integer.parseInt(bElement);
-
+
if (aNumber < bNumber)
return -1;
else if (aNumber > bNumber)
@@ -350,7 +350,7 @@ public final class SemanticVersionNumber
}
}
}
-
+
// we just tested the stuff that's in common, maybe someone has more
if (aSize < bSize)
return -1;
@@ -359,7 +359,7 @@ public final class SemanticVersionNumber
else
return 0;
}
-
+
/**
* Gets a version number from a string in the official format
*
@@ -377,12 +377,12 @@ public final class SemanticVersionNumber
throw new IllegalArgumentException(
String.format("Provided string \"%s\" is not a version number",
versionString));
-
+
// main parts
final int major = Integer.parseInt(m.group(1));
final int minor = Integer.parseInt(m.group(2));
final int patch = Integer.parseInt(m.group(3));
-
+
// pre release
final List<String> preRelease;
if (m.group(4) == null) {
@@ -390,7 +390,7 @@ public final class SemanticVersionNumber
} else {
preRelease = Arrays.asList(m.group(4).split("\\."));
}
-
+
// build metadata
final List<String> buildMetadata;
if (m.group(5) == null) {
@@ -398,12 +398,12 @@ public final class SemanticVersionNumber
} else {
buildMetadata = Arrays.asList(m.group(5).split("\\."));
}
-
+
// return number
return new SemanticVersionNumber(major, minor, patch, preRelease,
buildMetadata);
}
-
+
/**
* Tests whether a string is a valid Semantic Version string
*
@@ -415,7 +415,7 @@ public final class SemanticVersionNumber
public static final boolean isValidVersionString(String versionString) {
return VERSION_NUMBER.matcher(versionString).matches();
}
-
+
/**
* Creates a simple pre-release version number of the form
* MAJOR.MINOR.PATH-TYPE.NUMBER (e.g. 1.2.3-alpha.4).
@@ -454,7 +454,7 @@ public final class SemanticVersionNumber
List.of(preReleaseType, Integer.toString(preReleaseNumber)),
List.of());
}
-
+
/**
* Creates a {@code SemanticVersionNumber} instance without pre-release
* identifiers or build metadata.
@@ -484,14 +484,14 @@ public final class SemanticVersionNumber
return new SemanticVersionNumber(major, minor, patch, List.of(),
List.of());
}
-
+
// parts of the version number
private final int major;
private final int minor;
private final int patch;
private final List<String> preReleaseIdentifiers;
private final List<String> buildMetadata;
-
+
/**
* Creates a version number
*
@@ -511,7 +511,7 @@ public final class SemanticVersionNumber
this.preReleaseIdentifiers = preReleaseIdentifiers;
this.buildMetadata = buildMetadata;
}
-
+
/**
* @return build metadata (empty if there is none)
* @since v0.4.0
@@ -520,7 +520,7 @@ public final class SemanticVersionNumber
public List<String> buildMetadata() {
return Collections.unmodifiableList(this.buildMetadata);
}
-
+
/**
* Compares two version numbers according to the official Semantic Versioning
* order.
@@ -538,23 +538,23 @@ public final class SemanticVersionNumber
return -1;
else if (this.major > o.major)
return 1;
-
+
if (this.minor < o.minor)
return -1;
else if (this.minor > o.minor)
return 1;
-
+
if (this.patch < o.patch)
return -1;
else if (this.patch > o.patch)
return 1;
-
+
// now we just compare pre-release identifiers
// (remember: build metadata is ignored)
return SemanticVersionNumber.compareIdentifiers(
this.preReleaseIdentifiers, o.preReleaseIdentifiers);
}
-
+
/**
* Determines the compatibility of code written for this version to
* {@code other}. More specifically:
@@ -590,11 +590,11 @@ public final class SemanticVersionNumber
*/
public boolean compatibleWith(SemanticVersionNumber other) {
Objects.requireNonNull(other, "other may not be null");
-
+
return this.compareTo(other) == 0 || this.major != 0
&& this.major == other.major && this.compareTo(other) < 0;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -621,7 +621,7 @@ public final class SemanticVersionNumber
return false;
return true;
}
-
+
@Override
public int hashCode() {
final int prime = 31;
@@ -635,7 +635,7 @@ public final class SemanticVersionNumber
: this.preReleaseIdentifiers.hashCode());
return result;
}
-
+
/**
* @return true iff this version is stable (major version > 0 and not a
* pre-release)
@@ -645,7 +645,7 @@ public final class SemanticVersionNumber
public boolean isStable() {
return this.major > 0 && this.preReleaseIdentifiers.isEmpty();
}
-
+
/**
* @return the MAJOR version number, incremented when you make backwards
* incompatible API changes
@@ -655,7 +655,7 @@ public final class SemanticVersionNumber
public int majorVersion() {
return this.major;
}
-
+
/**
* @return the MINOR version number, incremented when you add backwards
* compatible functionality
@@ -665,7 +665,7 @@ public final class SemanticVersionNumber
public int minorVersion() {
return this.minor;
}
-
+
/**
* @return the PATCH version number, incremented when you make backwards
* compatible bug fixes
@@ -675,7 +675,7 @@ public final class SemanticVersionNumber
public int patchVersion() {
return this.patch;
}
-
+
/**
* @return identifiers describing this pre-release (empty if not a
* pre-release)
@@ -685,7 +685,7 @@ public final class SemanticVersionNumber
public List<String> preReleaseIdentifiers() {
return Collections.unmodifiableList(this.preReleaseIdentifiers);
}
-
+
/**
* Converts a version number to a string using the official SemVer format.
* The core of a version is MAJOR.MINOR.PATCH, without zero-padding. If
diff --git a/src/main/java/sevenUnits/utils/UncertainDouble.java b/src/main/java/sevenUnits/utils/UncertainDouble.java
index ac523b3..66d8103 100644
--- a/src/main/java/sevenUnits/utils/UncertainDouble.java
+++ b/src/main/java/sevenUnits/utils/UncertainDouble.java
@@ -23,7 +23,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * A double with an associated uncertainty value. For example, 3.2 ± 0.2.
+ * A double with an associated uncertainty value. For example, 3.2 � 0.2.
* <p>
* All methods in this class throw a NullPointerException if any of their
* arguments is null.
@@ -35,20 +35,20 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
* The exact value 0
*/
public static final UncertainDouble ZERO = UncertainDouble.of(0, 0);
-
+
static final String NUMBER_REGEX = "(\\d+(?:[\\.,]\\d+))";
-
+
/**
* A regular expression that can recognize toString forms
*/
static final Pattern TO_STRING = Pattern.compile(NUMBER_REGEX
- // optional "± [number]"
- + "(?:\\s*(?:±|\\+-)\\s*" + NUMBER_REGEX + ")?");
-
+ // optional "� [number]"
+ + "(?:\\s*(?:�|\\+-)\\s*" + NUMBER_REGEX + ")?");
+
/**
* Gets an UncertainDouble from a double string. The uncertainty of the
* double will be one of the lowest decimal place of the number. For example,
- * "12345.678" will become 12345.678 ± 0.001.
+ * "12345.678" will become 12345.678 � 0.001.
*
* @throws NumberFormatException if the argument is not a number
*
@@ -59,13 +59,13 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
final double uncertainty = Math.pow(10, -value.scale());
return UncertainDouble.of(value.doubleValue(), uncertainty);
}
-
+
/**
* Parses a string in the form of {@link UncertainDouble#toString(boolean)}
* and returns the corresponding {@code UncertainDouble} instance.
* <p>
* This method allows some alternative forms of the string representation,
- * such as using "+-" instead of "±".
+ * such as using "+-" instead of "�".
*
* @param s string to parse
* @return {@code UncertainDouble} instance
@@ -75,11 +75,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public static final UncertainDouble fromString(String s) {
Objects.requireNonNull(s, "s may not be null");
final Matcher matcher = TO_STRING.matcher(s);
-
+
if (!matcher.matches())
throw new IllegalArgumentException(
"Could not parse stirng \"" + s + "\".");
-
+
double value, uncertainty;
try {
value = Double.parseDouble(matcher.group(1));
@@ -87,7 +87,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
throw new IllegalArgumentException(
"String " + s + " not in correct format.");
}
-
+
final String uncertaintyString = matcher.group(2);
if (uncertaintyString == null) {
uncertainty = 0;
@@ -99,10 +99,10 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
"String " + s + " not in correct format.");
}
}
-
+
return UncertainDouble.of(value, uncertainty);
}
-
+
/**
* Gets an {@code UncertainDouble} from its value and <b>absolute</b>
* uncertainty.
@@ -112,7 +112,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public static final UncertainDouble of(double value, double uncertainty) {
return new UncertainDouble(value, uncertainty);
}
-
+
/**
* Gets an {@code UncertainDouble} from its value and <b>relative</b>
* uncertainty.
@@ -123,11 +123,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
double relativeUncertainty) {
return new UncertainDouble(value, value * relativeUncertainty);
}
-
+
private final double value;
-
+
private final double uncertainty;
-
+
/**
* @param value
* @param uncertainty
@@ -138,13 +138,13 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
// uncertainty should only ever be positive
this.uncertainty = Math.abs(uncertainty);
}
-
+
/**
* Compares this {@code UncertainDouble} with another
* {@code UncertainDouble}.
* <p>
- * This method only compares the values, not the uncertainties. So 3.1 ± 0.5
- * is considered less than 3.2 ± 0.5, even though they are equivalent.
+ * This method only compares the values, not the uncertainties. So 3.1 � 0.5
+ * is considered less than 3.2 � 0.5, even though they are equivalent.
* <p>
* <b>Note:</b> The natural ordering of this class is inconsistent with
* equals. Specifically, if two {@code UncertainDouble} instances {@code a}
@@ -156,7 +156,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final int compareTo(UncertainDouble o) {
return Double.compare(this.value, o.value);
}
-
+
/**
* Returns the quotient of {@code this} and {@code other}.
*
@@ -167,7 +167,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
return UncertainDouble.ofRelative(this.value / other.value, Math
.hypot(this.relativeUncertainty(), other.relativeUncertainty()));
}
-
+
/**
* Returns the quotient of {@code this} and the exact value {@code other}.
*
@@ -176,7 +176,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final UncertainDouble dividedByExact(double other) {
return UncertainDouble.of(this.value / other, this.uncertainty / other);
}
-
+
@Override
public final boolean equals(Object obj) {
if (this == obj)
@@ -190,7 +190,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
return false;
return true;
}
-
+
/**
* @param other another {@code UncertainDouble}
* @return true iff this and {@code other} are within each other's
@@ -202,7 +202,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
return Math.abs(this.value - other.value) <= Math.min(this.uncertainty,
other.uncertainty);
}
-
+
/**
* Gets the preferred scale for rounding a value for toString.
*
@@ -217,19 +217,19 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
// the value is rounded to the same number of decimal places as the
// uncertainty.
final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty);
-
+
// the scale that will give the uncertainty two decimal places
final int twoDecimalPlacesScale = bigUncertainty.scale()
- bigUncertainty.precision() + 2;
final BigDecimal roundedUncertainty = bigUncertainty
.setScale(twoDecimalPlacesScale, RoundingMode.HALF_EVEN);
-
+
if (roundedUncertainty.unscaledValue().intValue() >= 20)
return twoDecimalPlacesScale - 1; // one decimal place
else
return twoDecimalPlacesScale;
}
-
+
@Override
public final int hashCode() {
final int prime = 31;
@@ -238,7 +238,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
result = prime * result + Double.hashCode(this.uncertainty);
return result;
}
-
+
/**
* @return true iff the value has no uncertainty
*
@@ -247,7 +247,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final boolean isExact() {
return this.uncertainty == 0;
}
-
+
/**
* Returns the difference of {@code this} and {@code other}.
*
@@ -258,7 +258,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
return UncertainDouble.of(this.value - other.value,
Math.hypot(this.uncertainty, other.uncertainty));
}
-
+
/**
* Returns the difference of {@code this} and the exact value {@code other}.
*
@@ -267,7 +267,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final UncertainDouble minusExact(double other) {
return UncertainDouble.of(this.value - other, this.uncertainty);
}
-
+
/**
* Returns the sum of {@code this} and {@code other}.
*
@@ -278,7 +278,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
return UncertainDouble.of(this.value + other.value,
Math.hypot(this.uncertainty, other.uncertainty));
}
-
+
/**
* Returns the sum of {@code this} and the exact value {@code other}.
*
@@ -287,7 +287,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final UncertainDouble plusExact(double other) {
return UncertainDouble.of(this.value + other, this.uncertainty);
}
-
+
/**
* @return relative uncertainty
* @since 2020-09-07
@@ -295,7 +295,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final double relativeUncertainty() {
return this.uncertainty / this.value;
}
-
+
/**
* Returns the product of {@code this} and {@code other}.
*
@@ -306,7 +306,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
return UncertainDouble.ofRelative(this.value * other.value, Math
.hypot(this.relativeUncertainty(), other.relativeUncertainty()));
}
-
+
/**
* Returns the product of {@code this} and the exact value {@code other}.
*
@@ -315,7 +315,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final UncertainDouble timesExact(double other) {
return UncertainDouble.of(this.value * other, this.uncertainty * other);
}
-
+
/**
* Returns the result of {@code this} raised to the exponent {@code other}.
*
@@ -323,15 +323,15 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
*/
public final UncertainDouble toExponent(UncertainDouble other) {
Objects.requireNonNull(other, "other may not be null");
-
+
final double result = Math.pow(this.value, other.value);
final double relativeUncertainty = Math.hypot(
other.value * this.relativeUncertainty(),
Math.log(this.value) * other.uncertainty);
-
+
return UncertainDouble.ofRelative(result, relativeUncertainty);
}
-
+
/**
* Returns the result of {@code this} raised the exact exponent
* {@code other}.
@@ -342,7 +342,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
return UncertainDouble.ofRelative(Math.pow(this.value, other),
this.relativeUncertainty() * other);
}
-
+
/**
* Returns a string representation of this {@code UncertainDouble}.
* <p>
@@ -354,8 +354,8 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
* Examples:
*
* <pre>
- * UncertainDouble.of(3.27, 0.22).toString() = "3.3 ± 0.2"
- * UncertainDouble.of(3.27, 0.13).toString() = "3.27 ± 0.13"
+ * UncertainDouble.of(3.27, 0.22).toString() = "3.3 � 0.2"
+ * UncertainDouble.of(3.27, 0.13).toString() = "3.27 � 0.13"
* UncertainDouble.of(-5.01, 0).toString() = "-5.01"
* </pre>
*
@@ -365,12 +365,12 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final String toString() {
return this.toString(!this.isExact(), RoundingMode.HALF_EVEN);
}
-
+
/**
* Returns a string representation of this {@code UncertainDouble}.
* <p>
* If {@code showUncertainty} is true, the string will be of the form "VALUE
- * ± UNCERTAINTY", and if it is false the string will be of the form "VALUE"
+ * � UNCERTAINTY", and if it is false the string will be of the form "VALUE"
* <p>
* VALUE represents a string representation of this {@code UncertainDouble}'s
* value. If the uncertainty is non-zero, the string will be rounded to the
@@ -385,11 +385,11 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
*
* <pre>
* UncertainDouble.of(3.27, 0.22).toString(false) = "3.3"
- * UncertainDouble.of(3.27, 0.22).toString(true) = "3.3 ± 0.2"
+ * UncertainDouble.of(3.27, 0.22).toString(true) = "3.3 � 0.2"
* UncertainDouble.of(3.27, 0.13).toString(false) = "3.27"
- * UncertainDouble.of(3.27, 0.13).toString(true) = "3.27 ± 0.13"
+ * UncertainDouble.of(3.27, 0.13).toString(true) = "3.27 � 0.13"
* UncertainDouble.of(-5.01, 0).toString(false) = "-5.01"
- * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 ± 0.0"
+ * UncertainDouble.of(-5.01, 0).toString(true) = "-5.01 � 0.0"
* </pre>
*
* @since 2020-09-07
@@ -397,31 +397,31 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final String toString(boolean showUncertainty,
RoundingMode roundingMode) {
String valueString, uncertaintyString;
-
+
// generate the string representation of value and uncertainty
if (this.isExact()) {
uncertaintyString = "0.0";
valueString = Double.toString(this.value);
-
+
} else {
// round the value and uncertainty according to getDisplayScale()
final BigDecimal bigValue = BigDecimal.valueOf(this.value);
final BigDecimal bigUncertainty = BigDecimal.valueOf(this.uncertainty);
-
+
final int displayScale = this.getDisplayScale();
final BigDecimal roundedUncertainty = bigUncertainty
.setScale(displayScale, roundingMode);
final BigDecimal roundedValue = bigValue.setScale(displayScale,
roundingMode);
-
+
valueString = roundedValue.toString();
uncertaintyString = roundedUncertainty.toString();
}
-
- // return "value" or "value ± uncertainty" depending on showUncertainty
- return valueString + (showUncertainty ? " ± " + uncertaintyString : "");
+
+ // return "value" or "value � uncertainty" depending on showUncertainty
+ return valueString + (showUncertainty ? " � " + uncertaintyString : "");
}
-
+
/**
* @return absolute uncertainty
* @since 2020-09-07
@@ -429,7 +429,7 @@ public final class UncertainDouble implements Comparable<UncertainDouble> {
public final double uncertainty() {
return this.uncertainty;
}
-
+
/**
* @return value without uncertainty
* @since 2020-09-07
diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java
index b56356d..1fb2709 100644
--- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java
+++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java
@@ -45,16 +45,16 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> {
} else {
magnifying = false;
}
-
+
// if the first prefix is non-metric (including binary prefixes),
// assume we are using non-metric prefixes
// non-metric prefixes are allowed, but can't be repeated.
if (!Metric.DECIMAL_PREFIXES.contains(prefixes.get(0)))
return NO_REPETITION.test(prefixes);
-
+
int part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto,
// 2=deka,hecto,deci,centi
-
+
for (final UnitPrefix prefix : prefixes) {
// check that the current prefix is metric and appropriately
// magnifying/reducing
@@ -62,7 +62,7 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> {
return false;
if (magnifying != prefix.getMultiplier() > 1)
return false;
-
+
// check if the current prefix is correct
// since part is set *after* this check, part designates the state
// of the *previous* prefix
@@ -79,7 +79,7 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> {
// deka/hecto must be the last prefix, so this is always invalid
return false;
}
-
+
// set part
if (Metric.YOTTA.equals(prefix) || Metric.YOCTO.equals(prefix)) {
part = 0;
diff --git a/src/main/java/sevenUnitsGUI/DelegateListModel.java b/src/main/java/sevenUnitsGUI/DelegateListModel.java
index 5938b59..798383b 100644
--- a/src/main/java/sevenUnitsGUI/DelegateListModel.java
+++ b/src/main/java/sevenUnitsGUI/DelegateListModel.java
@@ -27,15 +27,17 @@ import javax.swing.AbstractListModel;
/**
* A list model that delegates to a list.
* <p>
- * 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.
+ * 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.
* </p>
*
* @author Adrien Hopkins
* @since 2019-01-14
* @since v0.1.0
*/
-final class DelegateListModel<E> extends AbstractListModel<E> implements List<E> {
+final class DelegateListModel<E> extends AbstractListModel<E>
+ implements List<E> {
/**
* @since 2019-01-14
* @since v0.1.0
@@ -62,8 +64,7 @@ final class DelegateListModel<E> extends AbstractListModel<E> implements List<E>
/**
* Creates the {@code DelegateListModel}.
*
- * @param delegate
- * list to delegate
+ * @param delegate list to delegate
* @since 2019-01-14
* @since v0.1.0
*/
@@ -101,7 +102,8 @@ final class DelegateListModel<E> extends AbstractListModel<E> implements List<E>
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.
+ return !c.isEmpty(); // Since this is a list, it will always change if c
+ // has elements.
}
@Override
diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java
index 5c39788..882c995 100644
--- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java
+++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java
@@ -30,14 +30,14 @@ public interface ExpressionConversionView extends View {
* @since 2021-12-15
*/
String getFromExpression();
-
+
/**
* @return unit expression to convert <em>to</em>
* @since v0.4.0
* @since 2021-12-15
*/
String getToExpression();
-
+
/**
* Shows the output of an expression conversion to the user.
*
diff --git a/src/main/java/sevenUnitsGUI/FilterComparator.java b/src/main/java/sevenUnitsGUI/FilterComparator.java
index c0a67e8..484a98f 100644
--- a/src/main/java/sevenUnitsGUI/FilterComparator.java
+++ b/src/main/java/sevenUnitsGUI/FilterComparator.java
@@ -50,7 +50,7 @@ final class FilterComparator<T> implements Comparator<T> {
* @since v0.2.0
*/
private final boolean caseSensitive;
-
+
/**
* Creates the {@code FilterComparator}.
*
@@ -61,7 +61,7 @@ final class FilterComparator<T> implements Comparator<T> {
public FilterComparator(final String filter) {
this(filter, null);
}
-
+
/**
* Creates the {@code FilterComparator}.
*
@@ -76,7 +76,7 @@ final class FilterComparator<T> implements Comparator<T> {
final Comparator<T> comparator) {
this(filter, comparator, false);
}
-
+
/**
* Creates the {@code FilterComparator}.
*
@@ -95,7 +95,7 @@ final class FilterComparator<T> implements Comparator<T> {
this.comparator = comparator;
this.caseSensitive = caseSensitive;
}
-
+
/**
* Compares two objects according to whether or not they match a filter.
* Objects whose string representation starts with the filter's text go
@@ -114,19 +114,19 @@ final class FilterComparator<T> implements Comparator<T> {
str0 = arg0.toString().toLowerCase();
str1 = arg1.toString().toLowerCase();
}
-
+
// elements that start with the filter always go first
if (str0.startsWith(this.filter) && !str1.startsWith(this.filter))
return -1;
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 (str0.contains(this.filter) && !str1.contains(this.filter))
return -1;
else if (!str0.contains(this.filter) && !str1.contains(this.filter))
return 1;
-
+
// other elements go last
if (this.comparator == null)
return str0.compareTo(str1);
diff --git a/src/main/java/sevenUnitsGUI/GridBagBuilder.java b/src/main/java/sevenUnitsGUI/GridBagBuilder.java
index 32e94d7..fdbaee7 100644
--- a/src/main/java/sevenUnitsGUI/GridBagBuilder.java
+++ b/src/main/java/sevenUnitsGUI/GridBagBuilder.java
@@ -30,13 +30,16 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code gridx} property.
* <p>
- * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has
- * <code>gridx=0</code>. 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
- * <code>RELATIVE</code> specifies that the component be placed immediately following the component that was added
- * to the container just before this component was added.
+ * Specifies the cell containing the leading edge of the component's display
+ * area, where the first cell in a row has <code>gridx=0</code>. 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 <code>RELATIVE</code> specifies that the component be
+ * placed immediately following the component that was added to the container
+ * just before this component was added.
* <p>
- * The default value is <code>RELATIVE</code>. <code>gridx</code> should be a non-negative value.
+ * The default value is <code>RELATIVE</code>. <code>gridx</code> should be a
+ * non-negative value.
*
* @serial
* @see #clone()
@@ -48,11 +51,13 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code gridy} property.
* <p>
- * Specifies the cell at the top of the component's display area, where the topmost cell has <code>gridy=0</code>.
- * The value <code>RELATIVE</code> specifies that the component be placed just below the component that was added to
- * the container just before this component was added.
+ * Specifies the cell at the top of the component's display area, where the
+ * topmost cell has <code>gridy=0</code>. The value <code>RELATIVE</code>
+ * specifies that the component be placed just below the component that was
+ * added to the container just before this component was added.
* <p>
- * The default value is <code>RELATIVE</code>. <code>gridy</code> should be a non-negative value.
+ * The default value is <code>RELATIVE</code>. <code>gridy</code> should be a
+ * non-negative value.
*
* @serial
* @see #clone()
@@ -65,9 +70,10 @@ final class GridBagBuilder {
* <p>
* Specifies the number of cells in a row for the component's display area.
* <p>
- * Use <code>REMAINDER</code> to specify that the component's display area will be from <code>gridx</code> to the
- * last cell in the row. Use <code>RELATIVE</code> to specify that the component's display area will be from
- * <code>gridx</code> to the next to the last one in its row.
+ * Use <code>REMAINDER</code> to specify that the component's display area
+ * will be from <code>gridx</code> to the last cell in the row. Use
+ * <code>RELATIVE</code> to specify that the component's display area will be
+ * from <code>gridx</code> to the next to the last one in its row.
* <p>
* <code>gridwidth</code> should be non-negative and the default value is 1.
*
@@ -80,13 +86,16 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code gridheight} property.
* <p>
- * Specifies the number of cells in a column for the component's display area.
+ * Specifies the number of cells in a column for the component's display
+ * area.
* <p>
- * Use <code>REMAINDER</code> to specify that the component's display area will be from <code>gridy</code> to the
- * last cell in the column. Use <code>RELATIVE</code> to specify that the component's display area will be from
- * <code>gridy</code> to the next to the last one in its column.
+ * Use <code>REMAINDER</code> to specify that the component's display area
+ * will be from <code>gridy</code> to the last cell in the column. Use
+ * <code>RELATIVE</code> to specify that the component's display area will be
+ * from <code>gridy</code> to the next to the last one in its column.
* <p>
- * <code>gridheight</code> should be a non-negative value and the default value is 1.
+ * <code>gridheight</code> should be a non-negative value and the default
+ * value is 1.
*
* @serial
* @see #clone()
@@ -99,15 +108,17 @@ final class GridBagBuilder {
* <p>
* Specifies how to distribute extra horizontal space.
* <p>
- * The grid bag layout manager calculates the weight of a column to be the maximum <code>weightx</code> 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.
+ * The grid bag layout manager calculates the weight of a column to be the
+ * maximum <code>weightx</code> 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.
* <p>
- * If all the weights are zero, all the extra space appears between the grids of the cell and the left and right
- * edges.
+ * If all the weights are zero, all the extra space appears between the grids
+ * of the cell and the left and right edges.
* <p>
- * The default value of this field is <code>0</code>. <code>weightx</code> should be a non-negative value.
+ * The default value of this field is <code>0</code>. <code>weightx</code>
+ * should be a non-negative value.
*
* @serial
* @see #clone()
@@ -120,15 +131,17 @@ final class GridBagBuilder {
* <p>
* Specifies how to distribute extra vertical space.
* <p>
- * The grid bag layout manager calculates the weight of a row to be the maximum <code>weighty</code> 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.
+ * The grid bag layout manager calculates the weight of a row to be the
+ * maximum <code>weighty</code> 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.
* <p>
- * If all the weights are zero, all the extra space appears between the grids of the cell and the top and bottom
- * edges.
+ * If all the weights are zero, all the extra space appears between the grids
+ * of the cell and the top and bottom edges.
* <p>
- * The default value of this field is <code>0</code>. <code>weighty</code> should be a non-negative value.
+ * The default value of this field is <code>0</code>. <code>weighty</code>
+ * should be a non-negative value.
*
* @serial
* @see #clone()
@@ -139,20 +152,26 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code anchor} property.
* <p>
- * This field is used when the component is smaller than its display area. It determines where, within the display
- * area, to place the component.
+ * This field is used when the component is smaller than its display area. It
+ * determines where, within the display area, to place the component.
* <p>
- * 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:
- * <code>CENTER</code>, <code>NORTH</code>, <code>NORTHEAST</code>, <code>EAST</code>, <code>SOUTHEAST</code>,
- * <code>SOUTH</code>, <code>SOUTHWEST</code>, <code>WEST</code>, and <code>NORTHWEST</code>. The orientation
- * relative values are: <code>PAGE_START</code>, <code>PAGE_END</code>, <code>LINE_START</code>,
- * <code>LINE_END</code>, <code>FIRST_LINE_START</code>, <code>FIRST_LINE_END</code>, <code>LAST_LINE_START</code>
- * and <code>LAST_LINE_END</code>. The baseline relative values are: <code>BASELINE</code>,
- * <code>BASELINE_LEADING</code>, <code>BASELINE_TRAILING</code>, <code>ABOVE_BASELINE</code>,
- * <code>ABOVE_BASELINE_LEADING</code>, <code>ABOVE_BASELINE_TRAILING</code>, <code>BELOW_BASELINE</code>,
- * <code>BELOW_BASELINE_LEADING</code>, and <code>BELOW_BASELINE_TRAILING</code>. The default value is
+ * 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: <code>CENTER</code>,
+ * <code>NORTH</code>, <code>NORTHEAST</code>, <code>EAST</code>,
+ * <code>SOUTHEAST</code>, <code>SOUTH</code>, <code>SOUTHWEST</code>,
+ * <code>WEST</code>, and <code>NORTHWEST</code>. The orientation relative
+ * values are: <code>PAGE_START</code>, <code>PAGE_END</code>,
+ * <code>LINE_START</code>, <code>LINE_END</code>,
+ * <code>FIRST_LINE_START</code>, <code>FIRST_LINE_END</code>,
+ * <code>LAST_LINE_START</code> and <code>LAST_LINE_END</code>. The baseline
+ * relative values are: <code>BASELINE</code>, <code>BASELINE_LEADING</code>,
+ * <code>BASELINE_TRAILING</code>, <code>ABOVE_BASELINE</code>,
+ * <code>ABOVE_BASELINE_LEADING</code>, <code>ABOVE_BASELINE_TRAILING</code>,
+ * <code>BELOW_BASELINE</code>, <code>BELOW_BASELINE_LEADING</code>, and
+ * <code>BELOW_BASELINE_TRAILING</code>. The default value is
* <code>CENTER</code>.
*
* @serial
@@ -164,17 +183,18 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code fill} property.
* <p>
- * 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.
+ * 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.
* <p>
* The following values are valid for <code>fill</code>:
*
* <ul>
* <li><code>NONE</code>: Do not resize the component.
- * <li><code>HORIZONTAL</code>: Make the component wide enough to fill its display area horizontally, but do not
- * change its height.
- * <li><code>VERTICAL</code>: Make the component tall enough to fill its display area vertically, but do not change
- * its width.
+ * <li><code>HORIZONTAL</code>: Make the component wide enough to fill its
+ * display area horizontally, but do not change its height.
+ * <li><code>VERTICAL</code>: Make the component tall enough to fill its
+ * display area vertically, but do not change its width.
* <li><code>BOTH</code>: Make the component fill its display area entirely.
* </ul>
* <p>
@@ -188,8 +208,8 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code insets} property.
* <p>
- * This field specifies the external padding of the component, the minimum amount of space between the component and
- * the edges of its display area.
+ * This field specifies the external padding of the component, the minimum
+ * amount of space between the component and the edges of its display area.
* <p>
* The default value is <code>new Insets(0, 0, 0, 0)</code>.
*
@@ -201,8 +221,9 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code ipadx} property.
* <p>
- * 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 <code>ipadx</code> pixels.
+ * 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 <code>ipadx</code> pixels.
* <p>
* The default value is <code>0</code>.
*
@@ -215,8 +236,9 @@ final class GridBagBuilder {
/**
* The built {@code GridBagConstraints}'s {@code ipady} property.
* <p>
- * 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 <code>ipady</code> pixels.
+ * 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 <code>ipady</code> pixels.
* <p>
* The default value is 0.
*
@@ -227,10 +249,8 @@ final class GridBagBuilder {
private int ipady;
/**
- * @param gridx
- * x position
- * @param gridy
- * y position
+ * @param gridx x position
+ * @param gridy y position
* @since 2018-11-30
* @since v0.1.0
*/
@@ -239,31 +259,25 @@ final class GridBagBuilder {
}
/**
- * @param gridx
- * x position
- * @param gridy
- * y position
- * @param gridwidth
- * number of cells occupied horizontally
- * @param gridheight
- * number of cells occupied vertically
+ * @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,
+ 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 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
@@ -274,9 +288,10 @@ final class GridBagBuilder {
* @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) {
+ 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;
@@ -297,8 +312,9 @@ final class GridBagBuilder {
* @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 new GridBagConstraints(this.gridx, this.gridy, this.gridwidth,
+ this.gridheight, this.weightx, this.weighty, this.anchor, this.fill,
+ this.insets, this.ipadx, this.ipady);
}
/**
@@ -401,8 +417,7 @@ final class GridBagBuilder {
}
/**
- * @param anchor
- * anchor to set
+ * @param anchor anchor to set
* @since 2018-11-30
* @since v0.1.0
*/
@@ -412,8 +427,7 @@ final class GridBagBuilder {
}
/**
- * @param fill
- * fill to set
+ * @param fill fill to set
* @since 2018-11-30
* @since v0.1.0
*/
@@ -423,8 +437,7 @@ final class GridBagBuilder {
}
/**
- * @param insets
- * insets to set
+ * @param insets insets to set
* @since 2018-11-30
* @since v0.1.0
*/
@@ -434,8 +447,7 @@ final class GridBagBuilder {
}
/**
- * @param ipadx
- * ipadx to set
+ * @param ipadx ipadx to set
* @since 2018-11-30
* @since v0.1.0
*/
@@ -445,8 +457,7 @@ final class GridBagBuilder {
}
/**
- * @param ipady
- * ipady to set
+ * @param ipady ipady to set
* @since 2018-11-30
* @since v0.1.0
*/
@@ -456,8 +467,7 @@ final class GridBagBuilder {
}
/**
- * @param weightx
- * weightx to set
+ * @param weightx weightx to set
* @since 2018-11-30
* @since v0.1.0
*/
@@ -467,8 +477,7 @@ final class GridBagBuilder {
}
/**
- * @param weighty
- * weighty to set
+ * @param weighty weighty to set
* @since 2018-11-30
* @since v0.1.0
*/
diff --git a/src/main/java/sevenUnitsGUI/Main.java b/src/main/java/sevenUnitsGUI/Main.java
index 998b373..ff61b3b 100644
--- a/src/main/java/sevenUnitsGUI/Main.java
+++ b/src/main/java/sevenUnitsGUI/Main.java
@@ -23,7 +23,7 @@ package sevenUnitsGUI;
* @since 2022-04-19
*/
public final class Main {
-
+
/**
* The main method that starts 7Units
*
@@ -34,5 +34,5 @@ public final class Main {
public static void main(String[] args) {
View.createTabbedView();
}
-
+
}
diff --git a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java
index a5034c9..69f09e6 100644
--- a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java
+++ b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java
@@ -46,7 +46,7 @@ public final class PrefixSearchRule implements
*/
public static final PrefixSearchRule NO_PREFIXES = getUniversalRule(
Set.of());
-
+
/**
* A rule that gives every unit a common set of prefixes.
*
@@ -54,7 +54,7 @@ public final class PrefixSearchRule implements
*/
public static final PrefixSearchRule COMMON_PREFIXES = getCoherentOnlyRule(
Set.of(Metric.MILLI, Metric.KILO));
-
+
/**
* A rule that gives every unit all metric prefixes.
*
@@ -62,7 +62,7 @@ public final class PrefixSearchRule implements
*/
public static final PrefixSearchRule ALL_METRIC_PREFIXES = getCoherentOnlyRule(
Metric.ALL_PREFIXES);
-
+
/**
* Gets a rule that applies the provided prefixes to coherent units only (as
* defined by {@link LinearUnit#isCoherent}), except the kilogram
@@ -78,7 +78,7 @@ public final class PrefixSearchRule implements
return new PrefixSearchRule(prefixes,
u -> u.isCoherent() && !u.getName().equals("kilogram"));
}
-
+
/**
* Gets a rule that applies the provided prefixes to all units.
*
@@ -91,17 +91,17 @@ public final class PrefixSearchRule implements
Set<UnitPrefix> prefixes) {
return new PrefixSearchRule(prefixes, u -> true);
}
-
+
/**
* The set of prefixes that will be applied to the unit.
*/
private final Set<UnitPrefix> prefixes;
-
+
/**
* Determines which units are given prefixes.
*/
private final Predicate<LinearUnit> prefixableUnitRule;
-
+
/**
* @param prefixes prefixes to add to units
* @param prefixableUnitRule function that determines which units get
@@ -114,7 +114,7 @@ public final class PrefixSearchRule implements
this.prefixes = Collections.unmodifiableSet(new HashSet<>(prefixes));
this.prefixableUnitRule = prefixableUnitRule;
}
-
+
@Override
public Map<String, LinearUnit> apply(Entry<String, LinearUnit> t) {
final Map<String, LinearUnit> outputUnits = new HashMap<>();
@@ -129,7 +129,7 @@ public final class PrefixSearchRule implements
}
return Collections.unmodifiableMap(outputUnits);
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -140,7 +140,7 @@ public final class PrefixSearchRule implements
return Objects.equals(this.prefixableUnitRule, other.prefixableUnitRule)
&& Objects.equals(this.prefixes, other.prefixes);
}
-
+
/**
* @return rule that determines which units get prefixes
* @since v0.4.0
@@ -149,7 +149,7 @@ public final class PrefixSearchRule implements
public Predicate<LinearUnit> getPrefixableUnitRule() {
return this.prefixableUnitRule;
}
-
+
/**
* @return the prefixes that are applied by this rule
* @since v0.4.0
@@ -158,12 +158,12 @@ public final class PrefixSearchRule implements
public Set<UnitPrefix> getPrefixes() {
return this.prefixes;
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.prefixableUnitRule, this.prefixes);
}
-
+
@Override
public String toString() {
return "Apply the following prefixes: " + this.prefixes;
diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java
index abdd1f6..eba8438 100644
--- a/src/main/java/sevenUnitsGUI/Presenter.java
+++ b/src/main/java/sevenUnitsGUI/Presenter.java
@@ -49,6 +49,9 @@ import sevenUnits.unit.UnitValue;
import sevenUnits.utils.Nameable;
import sevenUnits.utils.ObjectProduct;
import sevenUnits.utils.UncertainDouble;
+import sevenUnitsGUI.StandardDisplayRules.FixedDecimals;
+import sevenUnitsGUI.StandardDisplayRules.FixedPrecision;
+import sevenUnitsGUI.StandardDisplayRules.UncertaintyBased;
/**
* An object that handles interactions between the view and the backend code
@@ -57,16 +60,42 @@ import sevenUnits.utils.UncertainDouble;
* @since 2021-12-15
*/
public final class Presenter {
- /** The default place where settings are stored. */
- private static final Path DEFAULT_SETTINGS_FILEPATH = Path
- .of("settings.txt");
+ /**
+ * The place where settings are stored. Both this path and its parent
+ * directory may not exist.
+ */
+ private static final Path CONFIG_FILE = userConfigDir().resolve("SevenUnits")
+ .resolve("config.txt");
/** The default place where units are stored. */
private static final String DEFAULT_UNITS_FILEPATH = "/unitsfile.txt";
/** The default place where dimensions are stored. */
private static final String DEFAULT_DIMENSIONS_FILEPATH = "/dimensionfile.txt";
/** The default place where exceptions are stored. */
private static final String DEFAULT_EXCEPTIONS_FILEPATH = "/metric_exceptions.txt";
-
+
+ private static final Path userConfigDir() {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ final String envFolder = System.getenv("LOCALAPPDATA");
+ if (envFolder == null || "".equals(envFolder)) {
+ return Path.of(System.getenv("USERPROFILE"), "AppData", "Local");
+ } else {
+ return Path.of(envFolder);
+ }
+ } else {
+ final String envFolder = System.getenv("XDG_CONFIG_HOME");
+ if (envFolder == null || "".equals(envFolder)) {
+ return Path.of(System.getenv("HOME"), ".config");
+ } else {
+ return Path.of(envFolder);
+ }
+ }
+ }
+
+ /** Gets a Path from a pathname in the config file. */
+ private static Path pathFromConfig(String pathname) {
+ return CONFIG_FILE.getParent().resolve(pathname);
+ }
+
/**
* Adds default units and dimensions to a database.
*
@@ -88,14 +117,14 @@ public final class Presenter {
// nonlinear units - must be loaded manually
database.addUnit("tempCelsius", Metric.CELSIUS);
database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT);
-
+
// load initial dimensions
database.addDimension("Length", Metric.Dimensions.LENGTH);
database.addDimension("Mass", Metric.Dimensions.MASS);
database.addDimension("Time", Metric.Dimensions.TIME);
database.addDimension("Temperature", Metric.Dimensions.TEMPERATURE);
}
-
+
/**
* @return text in About file
* @since 2022-02-19
@@ -105,7 +134,7 @@ public final class Presenter {
.map(Presenter::withoutComments).collect(Collectors.joining("\n"))
.replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString());
}
-
+
/**
* Gets the text of a resource file as a set of strings (each one is one line
* of the text).
@@ -116,7 +145,7 @@ public final class Presenter {
*/
private static final List<String> getLinesFromResource(String filename) {
final List<String> lines = new ArrayList<>();
-
+
try (InputStream stream = inputStream(filename);
Scanner scanner = new Scanner(stream)) {
while (scanner.hasNextLine()) {
@@ -126,10 +155,10 @@ public final class Presenter {
throw new AssertionError(
"Error occurred while loading file " + filename, e);
}
-
+
return lines;
}
-
+
/**
* Gets an input stream for a resource file.
*
@@ -140,7 +169,7 @@ public final class Presenter {
private static final InputStream inputStream(String filepath) {
return Presenter.class.getResourceAsStream(filepath);
}
-
+
/**
* @return true iff a and b have any elements in common
* @since 2022-04-19
@@ -152,7 +181,7 @@ public final class Presenter {
}
return false;
}
-
+
/**
* @return {@code line} with any comments removed.
* @since 2021-03-13
@@ -161,25 +190,25 @@ public final class Presenter {
final int index = line.indexOf('#');
return index == -1 ? line : line.substring(0, index);
}
-
+
// ====== SETTINGS ======
-
+
/**
* The view that this presenter communicates with
*/
private final View view;
-
+
/**
* The database that this presenter communicates with (effectively the model)
*/
final UnitDatabase database;
-
+
/**
* The rule used for parsing input numbers. Any number-string inputted into
* this program will be parsed using this method. <b>Not implemented yet.</b>
*/
private Function<String, UncertainDouble> numberParsingRule;
-
+
/**
* The rule used for displaying the results of unit conversions. The result
* of unit conversions will be put into this function, and the resulting
@@ -187,41 +216,41 @@ public final class Presenter {
*/
private Function<UncertainDouble, String> numberDisplayRule = StandardDisplayRules
.uncertaintyBased();
-
+
/**
* A predicate that determines whether or not a certain combination of
* prefixes is allowed. If it returns false, a combination of prefixes will
* not be allowed. Prefixes are put in the list from right to left.
*/
private Predicate<List<UnitPrefix>> prefixRepetitionRule = DefaultPrefixRepetitionRule.NO_RESTRICTION;
-
+
/**
* A rule that accepts a prefixless name-unit pair and returns a map mapping
* names to prefixed versions of that unit (including the unit itself) that
* should be searchable.
*/
private Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule = PrefixSearchRule.NO_PREFIXES;
-
+
/**
* The set of units that is considered neither metric nor nonmetric for the
* purposes of the metric-imperial one-way conversion. These units are
* included in both From and To, even if One Way Conversion is enabled.
*/
private final Set<String> metricExceptions;
-
+
/**
* If this is true, views that show units as a list will have metric units
* removed from the From unit list and imperial/USC units removed from the To
* unit list.
*/
private boolean oneWayConversionEnabled = false;
-
+
/**
* If this is false, duplicate units and prefixes will be removed from the
* unit view in views that show units as a list to choose from.
*/
private boolean showDuplicates = false;
-
+
/**
* Creates a Presenter
*
@@ -232,14 +261,14 @@ public final class Presenter {
this.view = view;
this.database = new UnitDatabase();
addDefaults(this.database);
-
+
// load units and prefixes
try (final InputStream units = inputStream(DEFAULT_UNITS_FILEPATH)) {
this.database.loadUnitsFromStream(units);
} catch (final IOException e) {
throw new AssertionError("Loading of unitsfile.txt failed.", e);
}
-
+
// load dimensions
try (final InputStream dimensions = inputStream(
DEFAULT_DIMENSIONS_FILEPATH)) {
@@ -247,7 +276,7 @@ public final class Presenter {
} catch (final IOException e) {
throw new AssertionError("Loading of dimensionfile.txt failed.", e);
}
-
+
// load metric exceptions
try {
this.metricExceptions = new HashSet<>();
@@ -265,14 +294,16 @@ public final class Presenter {
throw new AssertionError("Loading of metric_exceptions.txt failed.",
e);
}
-
+
// set default settings temporarily
- this.loadSettings(DEFAULT_SETTINGS_FILEPATH);
-
+ if (Files.exists(CONFIG_FILE)) {
+ this.loadSettings(CONFIG_FILE);
+ }
+
// a Predicate that returns true iff the argument is a full base unit
final Predicate<Unit> isFullBase = unit -> unit instanceof LinearUnit
&& ((LinearUnit) unit).isBase();
-
+
// print out unit counts
System.out.printf(
"Successfully loaded %d units with %d unit names (%d base units).%n",
@@ -281,7 +312,7 @@ public final class Presenter {
this.database.unitMapPrefixless(false).values().stream()
.filter(isFullBase).count());
}
-
+
/**
* Applies a search rule to an entry in a name-unit map.
*
@@ -301,7 +332,7 @@ public final class Presenter {
} else
return Stream.of(e);
}
-
+
/**
* Converts from the view's input expression to its output expression.
* Displays an error message if any of the required fields are invalid.
@@ -315,10 +346,10 @@ public final class Presenter {
public void convertExpressions() {
if (this.view instanceof ExpressionConversionView) {
final ExpressionConversionView xcview = (ExpressionConversionView) this.view;
-
+
final String fromExpression = xcview.getFromExpression();
final String toExpression = xcview.getToExpression();
-
+
// expressions must not be empty
if (fromExpression.isEmpty()) {
this.view.showErrorMessage("Parse Error",
@@ -330,7 +361,7 @@ public final class Presenter {
"Please enter a unit expression in the To: box.");
return;
}
-
+
// evaluate expressions
final LinearUnitValue from;
final Unit to;
@@ -348,11 +379,11 @@ public final class Presenter {
"Could not recognize text in To entry: " + e.getMessage());
return;
}
-
+
// convert and show output
if (from.getUnit().canConvertTo(to)) {
final UncertainDouble uncertainValue;
-
+
// uncertainty is meaningless for non-linear units, so we will have
// to erase uncertainty information for them
if (to instanceof LinearUnit) {
@@ -362,7 +393,7 @@ public final class Presenter {
final double value = from.asUnitValue().convertTo(to).getValue();
uncertainValue = UncertainDouble.of(value, 0);
}
-
+
final UnitConversionRecord uc = UnitConversionRecord.valueOf(
fromExpression, toExpression, "",
this.numberDisplayRule.apply(uncertainValue));
@@ -372,12 +403,12 @@ public final class Presenter {
"Cannot convert between \"" + fromExpression + "\" and \""
+ toExpression + "\".");
}
-
+
} else
throw new UnsupportedOperationException(
"This function can only be called when the view is an ExpressionConversionView");
}
-
+
/**
* Converts from the view's input unit to its output unit. Displays an error
* message if any of the required fields are invalid.
@@ -391,11 +422,11 @@ public final class Presenter {
public void convertUnits() {
if (this.view instanceof UnitConversionView) {
final UnitConversionView ucview = (UnitConversionView) this.view;
-
+
final Optional<String> fromUnitOptional = ucview.getFromSelection();
final Optional<String> toUnitOptional = ucview.getToSelection();
final String inputValueString = ucview.getInputValue();
-
+
// extract values from optionals
final String fromUnitString, toUnitString;
if (fromUnitOptional.isPresent()) {
@@ -412,11 +443,11 @@ public final class Presenter {
"Please specify a To unit");
return;
}
-
+
// convert strings to data, checking if anything is invalid
final Unit fromUnit, toUnit;
final UncertainDouble uncertainValue;
-
+
if (this.database.containsUnitName(fromUnitString)) {
fromUnit = this.database.getUnit(fromUnitString);
} else
@@ -433,11 +464,11 @@ public final class Presenter {
"Invalid value " + inputValueString);
return;
}
-
+
if (!fromUnit.canConvertTo(toUnit))
throw this.viewError("Could not convert between %s and %s",
fromUnit, toUnit);
-
+
// convert - we will need to erase uncertainty for non-linear units, so
// we need to treat linear and non-linear units differently
final String outputValueString;
@@ -447,18 +478,18 @@ public final class Presenter {
final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear,
uncertainValue);
final LinearUnitValue converted = initialValue.convertTo(toLinear);
-
+
outputValueString = this.numberDisplayRule
.apply(converted.getValue());
} else {
final UnitValue initialValue = UnitValue.of(fromUnit,
uncertainValue.value());
final UnitValue converted = initialValue.convertTo(toUnit);
-
+
outputValueString = this.numberDisplayRule
.apply(UncertainDouble.of(converted.getValue(), 0));
}
-
+
ucview.showUnitConversionOutput(
UnitConversionRecord.valueOf(fromUnitString, toUnitString,
inputValueString, outputValueString));
@@ -466,7 +497,7 @@ public final class Presenter {
throw new UnsupportedOperationException(
"This function can only be called when the view is a UnitConversionView.");
}
-
+
/**
* @return true iff duplicate units are shown in unit lists
* @since 2022-03-30
@@ -474,7 +505,7 @@ public final class Presenter {
public boolean duplicatesShown() {
return this.showDuplicates;
}
-
+
/**
* Gets a name for this dimension using the database
*
@@ -489,7 +520,7 @@ public final class Presenter {
.filter(d -> d.equals(dimension)).findAny().map(Nameable::getName)
.orElse(dimension.toString(Nameable::getName));
}
-
+
/**
* @return the rule that is used by this presenter to convert numbers into
* strings
@@ -498,7 +529,7 @@ public final class Presenter {
public Function<UncertainDouble, String> getNumberDisplayRule() {
return this.numberDisplayRule;
}
-
+
/**
* @return the rule that is used by this presenter to convert strings into
* numbers
@@ -508,7 +539,7 @@ public final class Presenter {
private Function<String, UncertainDouble> getNumberParsingRule() {
return this.numberParsingRule;
}
-
+
/**
* @return the rule that determines whether a set of prefixes is valid
* @since 2022-04-19
@@ -516,7 +547,7 @@ public final class Presenter {
public Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
return this.prefixRepetitionRule;
}
-
+
/**
* @return the rule that determines which units are prefixed
* @since 2022-07-08
@@ -524,7 +555,7 @@ public final class Presenter {
public Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> getSearchRule() {
return this.searchRule;
}
-
+
/**
* @return a search rule that shows all single prefixes
* @since 2022-07-08
@@ -533,7 +564,7 @@ public final class Presenter {
return PrefixSearchRule.getCoherentOnlyRule(
new HashSet<>(this.database.prefixMap(true).values()));
}
-
+
/**
* @return the view associated with this presenter
* @since 2022-04-19
@@ -541,7 +572,7 @@ public final class Presenter {
public View getView() {
return this.view;
}
-
+
/**
* @return whether or not the provided unit is semi-metric (i.e. an
* exception)
@@ -557,7 +588,7 @@ public final class Presenter {
&& this.metricExceptions.contains(symbol.orElseThrow())
|| sharesAnyElements(this.metricExceptions, u.getOtherNames());
}
-
+
/**
* Loads settings from the user's settings file and applies them to the
* presenter.
@@ -566,58 +597,124 @@ public final class Presenter {
* @since 2021-12-15
*/
void loadSettings(Path settingsFile) {
- try {
- // read file line by line
- final int lineNum = 0;
- for (final String line : Files.readAllLines(settingsFile)) {
- final int equalsIndex = line.indexOf('=');
- if (equalsIndex == -1)
- throw new IllegalStateException(
- "Settings file is malformed at line " + lineNum);
-
- final String param = line.substring(0, equalsIndex);
- final String value = line.substring(equalsIndex + 1);
-
- switch (param) {
- // set manually to avoid the unnecessary saving of the non-manual
- // methods
- case "number_display_rule":
- this.numberDisplayRule = StandardDisplayRules
- .getStandardRule(value);
- break;
- case "prefix_rule":
- this.prefixRepetitionRule = DefaultPrefixRepetitionRule
- .valueOf(value);
- this.database.setPrefixRepetitionRule(this.prefixRepetitionRule);
- break;
- case "one_way":
- this.oneWayConversionEnabled = Boolean.valueOf(value);
- break;
- case "include_duplicates":
- this.showDuplicates = Boolean.valueOf(value);
- break;
- case "search_prefix_rule":
- if (PrefixSearchRule.NO_PREFIXES.toString().equals(value)) {
- this.searchRule = PrefixSearchRule.NO_PREFIXES;
- } else if (PrefixSearchRule.COMMON_PREFIXES.toString()
- .equals(value)) {
- this.searchRule = PrefixSearchRule.COMMON_PREFIXES;
- } else {
- this.searchRule = this.getUniversalSearchRule();
- }
- break;
- default:
- System.err.printf("Warning: unrecognized setting \"%s\".%n",
- param);
- break;
- }
- }
- if (this.view.getPresenter() != null) {
- this.updateView();
+ for (Map.Entry<String, String> setting : settingsFromFile(settingsFile)) {
+ final String value = setting.getValue();
+
+ switch (setting.getKey()) {
+ // set manually to avoid the unnecessary saving of the non-manual
+ // methods
+ case "custom_dimension_file":
+ this.database.loadDimensionFile(pathFromConfig(value));
+ break;
+ case "custom_exception_file":
+ this.loadExceptionFile(pathFromConfig(value));
+ break;
+ case "custom_unit_file":
+ this.database.loadUnitsFile(pathFromConfig(value));
+ break;
+ case "number_display_rule":
+ this.setDisplayRuleFromString(value);
+ break;
+ case "prefix_rule":
+ this.prefixRepetitionRule = DefaultPrefixRepetitionRule
+ .valueOf(value);
+ this.database.setPrefixRepetitionRule(this.prefixRepetitionRule);
+ break;
+ case "one_way":
+ this.oneWayConversionEnabled = Boolean.valueOf(value);
+ break;
+ case "include_duplicates":
+ this.showDuplicates = Boolean.valueOf(value);
+ break;
+ case "search_prefix_rule":
+ this.setSearchRuleFromString(value);
+ break;
+ default:
+ System.err.printf("Warning: unrecognized setting \"%s\".%n",
+ setting.getKey());
+ break;
}
- } catch (final IOException e) {}
+ }
+
+ if (this.view.getPresenter() != null) {
+ this.updateView();
+ }
+ }
+
+ private List<Map.Entry<String, String>> settingsFromFile(Path settingsFile) {
+ try (Stream<String> lines = Files.lines(settingsFile)) {
+ return lines.map(Presenter::withoutComments)
+ .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine)
+ .toList();
+ } catch (final IOException e) {
+ this.view.showErrorMessage("Settings Loading Error",
+ "Error loading settings file. Using default settings.");
+ return null;
+ }
}
-
+
+ private static Map.Entry<String, String> parseSettingLine(String line) {
+ final int equalsIndex = line.indexOf('=');
+ if (equalsIndex == -1)
+ throw new IllegalStateException(
+ "Settings file is malformed at line: " + line);
+
+ final String param = line.substring(0, equalsIndex);
+ final String value = line.substring(equalsIndex + 1);
+
+ return Map.entry(param, value);
+ }
+
+ private void setSearchRuleFromString(String ruleString) {
+ switch (ruleString) {
+ case "NO_PREFIXES":
+ this.searchRule = PrefixSearchRule.NO_PREFIXES;
+ break;
+ case "COMMON_PREFIXES":
+ this.searchRule = PrefixSearchRule.COMMON_PREFIXES;
+ break;
+ case "ALL_METRIC_PREFIXES":
+ this.searchRule = PrefixSearchRule.ALL_METRIC_PREFIXES;
+ break;
+ default:
+ System.err.printf(
+ "Warning: unrecognized value for search_prefix_rule: %s\n",
+ ruleString);
+ }
+ }
+
+ private void setDisplayRuleFromString(String ruleString) {
+ String[] tokens = ruleString.split(" ");
+ switch (tokens[0]) {
+ case "FIXED_DECIMALS":
+ final int decimals = Integer.parseInt(tokens[1]);
+ this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals);
+ break;
+ case "FIXED_PRECISION":
+ final int sigDigs = Integer.parseInt(tokens[1]);
+ this.numberDisplayRule = StandardDisplayRules.fixedPrecision(sigDigs);
+ break;
+ case "UNCERTAINTY_BASED":
+ this.numberDisplayRule = StandardDisplayRules.uncertaintyBased();
+ break;
+ default:
+ this.numberDisplayRule = StandardDisplayRules
+ .getStandardRule(ruleString);
+ break;
+ }
+ }
+
+ private void loadExceptionFile(Path exceptionFile) {
+ try (Stream<String> lines = Files.lines(exceptionFile)) {
+ lines.map(Presenter::withoutComments)
+ .forEach(this.metricExceptions::add);
+ } catch (IOException e) {
+ this.view.showErrorMessage("File Load Error",
+ "Error loading configured metric exception file \""
+ + exceptionFile + "\": " + e.getLocalizedMessage());
+ }
+ }
+
/**
* @return true iff the One-Way Conversion feature is available (views that
* show units as a list will have metric units removed from the From
@@ -628,7 +725,7 @@ public final class Presenter {
public boolean oneWayConversionEnabled() {
return this.oneWayConversionEnabled;
}
-
+
/**
* Completes creation of the presenter. This part of the initialization
* depends on the view's functions, so it cannot be run if the components
@@ -642,10 +739,10 @@ public final class Presenter {
final UnitConversionView ucview = (UnitConversionView) this.view;
ucview.setDimensionNames(this.database.dimensionMap().keySet());
}
-
+
this.updateView();
}
-
+
void prefixSelected() {
final Optional<String> selectedPrefixName = this.view
.getViewedPrefixName();
@@ -657,42 +754,81 @@ public final class Presenter {
.ifPresent(prefix -> this.view.showPrefix(prefix.getNameSymbol(),
String.valueOf(prefix.getMultiplier())));
}
-
+
/**
- * Saves the presenter's current settings to its default filepath.
+ * Saves the presenter's current settings to the config file, creating it if
+ * it doesn't exist.
*
+ * @return false iff the presenter could not write to the file
* @since 2022-04-19
*/
- public void saveSettings() {
- this.saveSettings(DEFAULT_SETTINGS_FILEPATH);
+ public boolean saveSettings() {
+ final Path configDir = CONFIG_FILE.getParent();
+ if (!Files.exists(configDir)) {
+ try {
+ Files.createDirectories(configDir);
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ return this.writeSettings(CONFIG_FILE);
}
-
+
/**
* Saves the presenter's settings to the user settings file.
*
* @param settingsFile file settings should be saved to
* @since 2021-12-15
*/
- void saveSettings(Path settingsFile) {
+ boolean writeSettings(Path settingsFile) {
try (BufferedWriter writer = Files.newBufferedWriter(settingsFile)) {
writer.write(String.format("number_display_rule=%s\n",
- this.numberDisplayRule));
+ displayRuleToString(this.numberDisplayRule)));
writer.write(
String.format("prefix_rule=%s\n", this.prefixRepetitionRule));
writer.write(
String.format("one_way=%s\n", this.oneWayConversionEnabled));
writer.write(
String.format("include_duplicates=%s\n", this.showDuplicates));
- writer.write(
- String.format("search_prefix_rule=%s\n", this.searchRule));
+ writer.write(String.format("search_prefix_rule=%s\n",
+ searchRuleToString(this.searchRule)));
+ return true;
} catch (final IOException e) {
e.printStackTrace();
this.view.showErrorMessage("I/O Error",
"Error occurred while saving settings: "
+ e.getLocalizedMessage());
+ return false;
}
}
-
+
+ private static String searchRuleToString(
+ Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
+ if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) {
+ return "NO_PREFIXES";
+ } else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) {
+ return "COMMON_PREFIXES";
+ } else if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule)) {
+ return "ALL_METRIC_PREFIXES";
+ } else
+ return searchRule.toString();
+ }
+
+ private static String displayRuleToString(
+ Function<UncertainDouble, String> numberDisplayRule) {
+ if (numberDisplayRule instanceof FixedDecimals) {
+ return String.format("FIXED_DECIMALS %d",
+ ((FixedDecimals) numberDisplayRule).decimalPlaces());
+ } else if (numberDisplayRule instanceof FixedPrecision) {
+ return String.format("FIXED_PRECISION %d",
+ ((FixedPrecision) numberDisplayRule).significantFigures());
+ } else if (numberDisplayRule instanceof UncertaintyBased) {
+ return "UNCERTAINTY_BASED";
+ } else
+ return numberDisplayRule.toString();
+ }
+
/**
* @param numberDisplayRule the new rule that will be used by this presenter
* to convert numbers into strings
@@ -702,7 +838,7 @@ public final class Presenter {
Function<UncertainDouble, String> numberDisplayRule) {
this.numberDisplayRule = numberDisplayRule;
}
-
+
/**
* @param numberParsingRule the new rule that will be used by this presenter
* to convert strings into numbers
@@ -713,7 +849,7 @@ public final class Presenter {
Function<String, UncertainDouble> numberParsingRule) {
this.numberParsingRule = numberParsingRule;
}
-
+
/**
* @param oneWayConversionEnabled whether not one-way conversion should be
* enabled
@@ -724,7 +860,7 @@ public final class Presenter {
this.oneWayConversionEnabled = oneWayConversionEnabled;
this.updateView();
}
-
+
/**
* @param prefixRepetitionRule the rule that determines whether a set of
* prefixes is valid
@@ -735,7 +871,7 @@ public final class Presenter {
this.prefixRepetitionRule = prefixRepetitionRule;
this.database.setPrefixRepetitionRule(prefixRepetitionRule);
}
-
+
/**
* @param searchRule A rule that accepts a prefixless name-unit pair and
* returns a map mapping names to prefixed versions of that
@@ -747,7 +883,7 @@ public final class Presenter {
Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
this.searchRule = searchRule;
}
-
+
/**
* @param showDuplicateUnits whether or not duplicate units should be shown
* @since 2022-03-30
@@ -756,7 +892,7 @@ public final class Presenter {
this.showDuplicates = showDuplicateUnits;
this.updateView();
}
-
+
/**
* Shows a unit in the unit viewer
*
@@ -772,7 +908,7 @@ public final class Presenter {
final var unitType = UnitType.getType(u, this::isSemiMetric);
this.view.showUnit(nameSymbol, definition, dimensionString, unitType);
}
-
+
/**
* Runs whenever a unit name is selected in the unit viewer. Gets the
* description of a unit and displays it.
@@ -788,7 +924,7 @@ public final class Presenter {
: null);
selectedUnit.ifPresent(this::showUnit);
}
-
+
/**
* Updates the view's From and To units, if it has some
*
@@ -798,19 +934,19 @@ public final class Presenter {
if (this.view instanceof UnitConversionView) {
final UnitConversionView ucview = (UnitConversionView) this.view;
final var selectedDimensionName = ucview.getSelectedDimensionName();
-
+
// load units & prefixes into viewers
this.view.setViewableUnitNames(
this.database.unitMapPrefixless(this.showDuplicates).keySet());
this.view.setViewablePrefixNames(
this.database.prefixMap(this.showDuplicates).keySet());
-
+
// get From and To units
var fromUnits = this.database.unitMapPrefixless(this.showDuplicates)
.entrySet().stream();
var toUnits = this.database.unitMapPrefixless(this.showDuplicates)
.entrySet().stream();
-
+
// filter by dimension, if one is selected
if (selectedDimensionName.isPresent()) {
final var viewDimension = this.database
@@ -820,7 +956,7 @@ public final class Presenter {
toUnits = toUnits.filter(
u -> viewDimension.equals(u.getValue().getDimension()));
}
-
+
// filter by unit type, if desired
if (this.oneWayConversionEnabled) {
fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(),
@@ -828,7 +964,7 @@ public final class Presenter {
toUnits = toUnits.filter(u -> UnitType.getType(u.getValue(),
this::isSemiMetric) != UnitType.NON_METRIC);
}
-
+
// set unit names
ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule)
.map(Map.Entry::getKey).collect(Collectors.toSet()));
@@ -836,7 +972,7 @@ public final class Presenter {
.map(Map.Entry::getKey).collect(Collectors.toSet()));
}
}
-
+
/**
* @param message message to add
* @param args string formatting arguments for message
diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java
index 9b41601..8fba459 100644
--- a/src/main/java/sevenUnitsGUI/SearchBoxList.java
+++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java
@@ -40,13 +40,13 @@ import javax.swing.JTextField;
* @since v0.2.0
*/
final class SearchBoxList<E> 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.
*
@@ -54,7 +54,7 @@ final class SearchBoxList<E> extends JPanel {
* @since v0.2.0
*/
private static final String EMPTY_TEXT = "Search...";
-
+
/**
* The color to use for an empty foreground.
*
@@ -62,24 +62,24 @@ final class SearchBoxList<E> extends JPanel {
* @since v0.2.0
*/
private static final Color EMPTY_FOREGROUND = new Color(192, 192, 192);
-
+
// the components
private final Collection<E> itemsToFilter;
private final DelegateListModel<E> listModel;
private final JTextField searchBox;
private final JList<E> 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<E> customSearchFilter = o -> true;
private final Comparator<E> defaultOrdering;
private final boolean caseSensitive;
-
+
/**
* Creates an empty SearchBoxList
*
@@ -88,7 +88,7 @@ final class SearchBoxList<E> extends JPanel {
public SearchBoxList() {
this(List.of(), null, false);
}
-
+
/**
* Creates the {@code SearchBoxList}.
*
@@ -98,7 +98,7 @@ final class SearchBoxList<E> extends JPanel {
public SearchBoxList(final Collection<E> itemsToFilter) {
this(itemsToFilter, null, false);
}
-
+
/**
* Creates the {@code SearchBoxList}.
*
@@ -116,35 +116,35 @@ final class SearchBoxList<E> extends JPanel {
this.itemsToFilter = new ArrayList<>(itemsToFilter);
this.defaultOrdering = defaultOrdering;
this.caseSensitive = caseSensitive;
-
+
// 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.
*
@@ -155,7 +155,7 @@ final class SearchBoxList<E> extends JPanel {
public void addSearchFilter(final Predicate<E> filter) {
this.customSearchFilter = this.customSearchFilter.and(filter);
}
-
+
/**
* Resets the search filter.
*
@@ -165,7 +165,7 @@ final class SearchBoxList<E> extends JPanel {
public void clearSearchFilters() {
this.customSearchFilter = o -> true;
}
-
+
/**
* @return items available in search list, including items that are hidden by
* the search filter
@@ -174,7 +174,7 @@ final class SearchBoxList<E> extends JPanel {
public Collection<E> getItems() {
return Collections.unmodifiableCollection(this.itemsToFilter);
}
-
+
/**
* @return this component's search box component
* @since 2019-04-14
@@ -183,7 +183,7 @@ final class SearchBoxList<E> extends JPanel {
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
@@ -198,7 +198,7 @@ final class SearchBoxList<E> extends JPanel {
return item -> item.toString().toLowerCase()
.contains(searchText.toLowerCase());
}
-
+
/**
* @return this component's list component
* @since 2019-04-14
@@ -207,7 +207,7 @@ final class SearchBoxList<E> extends JPanel {
public final JList<E> getSearchList() {
return this.searchItems;
}
-
+
/**
* @return index selected in item list, -1 if no selection
* @since 2019-04-14
@@ -216,7 +216,7 @@ final class SearchBoxList<E> extends JPanel {
public int getSelectedIndex() {
return this.searchItems.getSelectedIndex();
}
-
+
/**
* @return value selected in item list
* @since 2019-04-13
@@ -225,7 +225,7 @@ final class SearchBoxList<E> extends JPanel {
public Optional<E> getSelectedValue() {
return Optional.ofNullable(this.searchItems.getSelectedValue());
}
-
+
/**
* Re-applies the filters.
*
@@ -238,21 +238,21 @@ final class SearchBoxList<E> extends JPanel {
final FilterComparator<E> comparator = new FilterComparator<>(searchText,
this.defaultOrdering, this.caseSensitive);
final Predicate<E> searchFilter = this.getSearchFilter(searchText);
-
+
this.listModel.clear();
this.itemsToFilter.forEach(item -> {
if (searchFilter.test(item)) {
this.listModel.add(item);
}
});
-
+
// applies the custom filters
this.listModel.removeIf(this.customSearchFilter.negate());
-
+
// sorts the remaining items
this.listModel.sort(comparator);
}
-
+
/**
* Runs whenever the search box gains focus.
*
@@ -267,7 +267,7 @@ final class SearchBoxList<E> extends JPanel {
this.searchBox.setForeground(Color.BLACK);
}
}
-
+
/**
* Runs whenever the search box loses focus.
*
@@ -282,7 +282,7 @@ final class SearchBoxList<E> extends JPanel {
this.searchBox.setForeground(EMPTY_FOREGROUND);
}
}
-
+
/**
* Runs whenever the text in the search box is changed.
* <p>
@@ -301,7 +301,7 @@ final class SearchBoxList<E> extends JPanel {
final FilterComparator<E> comparator = new FilterComparator<>(searchText,
this.defaultOrdering, this.caseSensitive);
final Predicate<E> searchFilter = this.getSearchFilter(searchText);
-
+
// initialize list with items that match the filter then sort
this.listModel.clear();
this.itemsToFilter.forEach(string -> {
@@ -309,14 +309,14 @@ final class SearchBoxList<E> extends JPanel {
this.listModel.add(string);
}
});
-
+
// applies the custom filters
this.listModel.removeIf(this.customSearchFilter.negate());
-
+
// sorts the remaining items
this.listModel.sort(comparator);
}
-
+
/**
* Resets the search box list's contents to the provided items, removing any
* old items
@@ -329,7 +329,7 @@ final class SearchBoxList<E> extends JPanel {
this.itemsToFilter.addAll(newItems);
this.reapplyFilter();
}
-
+
/**
* Manually updates the search box's item list.
*
diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
index cc69d31..d00263b 100644
--- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
+++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
@@ -46,7 +46,7 @@ public final class StandardDisplayRules {
* The number of places to round to.
*/
private final int decimalPlaces;
-
+
/**
* @param decimalPlaces
* @since 2022-04-18
@@ -54,14 +54,14 @@ public final class StandardDisplayRules {
private FixedDecimals(int decimalPlaces) {
this.decimalPlaces = decimalPlaces;
}
-
+
@Override
public String apply(UncertainDouble t) {
final var toRound = new BigDecimal(t.value());
return toRound.setScale(this.decimalPlaces, RoundingMode.HALF_EVEN)
.toPlainString();
}
-
+
/**
* @return the number of decimal places this rule rounds to
* @since 2022-04-18
@@ -69,7 +69,7 @@ public final class StandardDisplayRules {
public int decimalPlaces() {
return this.decimalPlaces;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -81,18 +81,18 @@ public final class StandardDisplayRules {
return false;
return true;
}
-
+
@Override
public int hashCode() {
return 31 + this.decimalPlaces;
}
-
+
@Override
public String toString() {
return "Round to " + this.decimalPlaces + " decimal places";
}
}
-
+
/**
* A rule that rounds to a fixed number of significant digits.
*
@@ -103,12 +103,12 @@ public final class StandardDisplayRules {
implements Function<UncertainDouble, String> {
public static final Pattern TO_STRING_PATTERN = Pattern
.compile("Round to (\\d+) significant figures");
-
+
/**
* The number of significant figures to round to.
*/
private final MathContext mathContext;
-
+
/**
* @param significantFigures
* @since 2022-04-18
@@ -117,13 +117,13 @@ public final class StandardDisplayRules {
this.mathContext = new MathContext(significantFigures,
RoundingMode.HALF_EVEN);
}
-
+
@Override
public String apply(UncertainDouble t) {
final var toRound = new BigDecimal(t.value());
return toRound.round(this.mathContext).toString();
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -138,13 +138,13 @@ public final class StandardDisplayRules {
return false;
return true;
}
-
+
@Override
public int hashCode() {
return 127
+ (this.mathContext == null ? 0 : this.mathContext.hashCode());
}
-
+
/**
* @return the number of significant figures this rule rounds to
* @since 2022-04-18
@@ -152,14 +152,14 @@ public final class StandardDisplayRules {
public int significantFigures() {
return this.mathContext.getPrecision();
}
-
+
@Override
public String toString() {
return "Round to " + this.mathContext.getPrecision()
+ " significant figures";
}
}
-
+
/**
* A rounding rule that rounds based on UncertainDouble's toString method.
* This means the output will have around as many significant figures as the
@@ -170,25 +170,26 @@ public final class StandardDisplayRules {
*/
public static final class UncertaintyBased
implements Function<UncertainDouble, String> {
- private UncertaintyBased() {}
-
+ private UncertaintyBased() {
+ }
+
@Override
public String apply(UncertainDouble t) {
return t.toString(false, RoundingMode.HALF_EVEN);
}
-
+
@Override
public String toString() {
return "Uncertainty-Based Rounding";
}
}
-
+
/**
* For now, I want this to be a singleton. I might want to add a parameter
* later, so I won't make it an enum.
*/
private static final UncertaintyBased UNCERTAINTY_BASED_ROUNDING_RULE = new UncertaintyBased();
-
+
/**
* @param decimalPlaces decimal places to round to
* @return a rounding rule that rounds to fixed number of decimal places
@@ -198,7 +199,7 @@ public final class StandardDisplayRules {
public static final FixedDecimals fixedDecimals(int decimalPlaces) {
return new FixedDecimals(decimalPlaces);
}
-
+
/**
* @param significantFigures significant figures to round to
* @return a rounding rule that rounds to a fixed number of significant
@@ -209,7 +210,7 @@ public final class StandardDisplayRules {
public static final FixedPrecision fixedPrecision(int significantFigures) {
return new FixedPrecision(significantFigures);
}
-
+
/**
* Gets one of the standard rules from its string representation.
*
@@ -224,23 +225,23 @@ public final class StandardDisplayRules {
String ruleToString) {
if (UNCERTAINTY_BASED_ROUNDING_RULE.toString().equals(ruleToString))
return UNCERTAINTY_BASED_ROUNDING_RULE;
-
+
// test if it is a fixed-places rule
final var placesMatch = FixedDecimals.TO_STRING_PATTERN
.matcher(ruleToString);
if (placesMatch.matches())
return new FixedDecimals(Integer.valueOf(placesMatch.group(1)));
-
+
// test if it is a fixed-sig-fig rule
final var sigFigMatch = FixedPrecision.TO_STRING_PATTERN
.matcher(ruleToString);
if (sigFigMatch.matches())
return new FixedPrecision(Integer.valueOf(sigFigMatch.group(1)));
-
+
throw new IllegalArgumentException(
"Provided string does not match any given rules.");
}
-
+
/**
* @return an UncertainDouble-based rounding rule
* @since v0.4.0
@@ -249,6 +250,7 @@ public final class StandardDisplayRules {
public static final UncertaintyBased uncertaintyBased() {
return UNCERTAINTY_BASED_ROUNDING_RULE;
}
-
- private StandardDisplayRules() {}
+
+ private StandardDisplayRules() {
+ }
}
diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java
index 6181eae..997acc3 100644
--- a/src/main/java/sevenUnitsGUI/TabbedView.java
+++ b/src/main/java/sevenUnitsGUI/TabbedView.java
@@ -78,7 +78,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
*/
private static final class JComboBoxItemSet<E> extends AbstractSet<E> {
private final JComboBox<E> comboBox;
-
+
/**
* @param comboBox combo box to get items from
* @since 2022-02-19
@@ -86,17 +86,17 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
public JComboBoxItemSet(JComboBox<E> comboBox) {
this.comboBox = comboBox;
}
-
+
@Override
public Iterator<E> iterator() {
return new Iterator<>() {
private int index = 0;
-
+
@Override
public boolean hasNext() {
return this.index < JComboBoxItemSet.this.size();
}
-
+
@Override
public E next() {
if (this.hasNext())
@@ -107,14 +107,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
}
};
}
-
+
@Override
public int size() {
return this.comboBox.getItemCount();
}
-
+
}
-
+
/**
* The standard types of rounding, corresponding to the options on the
* TabbedView's settings panel.
@@ -139,7 +139,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
*/
UNCERTAINTY;
}
-
+
/**
* Creates a TabbedView.
*
@@ -153,14 +153,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
@SuppressWarnings("unused")
final View view = new TabbedView();
}
-
+
/** The Presenter that handles this View */
final Presenter presenter;
/** The frame that this view lives on */
final JFrame frame;
/** The tabbed pane that contains all of the components */
final JTabbedPane masterPane;
-
+
// DIMENSION-BASED CONVERTER
/** The combo box that selects dimensions */
final JComboBox<String> dimensionSelector;
@@ -174,7 +174,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
final JButton convertUnitButton;
/** The output area in the dimension-based converter */
final JTextArea unitOutput;
-
+
// EXPRESSION-BASED CONVERTER
/** The "From" entry in the conversion panel */
final JTextField fromEntry;
@@ -184,7 +184,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
final JButton convertExpressionButton;
/** The output area in the conversion panel */
final JTextArea expressionOutput;
-
+
// UNIT AND PREFIX VIEWERS
/** The searchable list of unit names in the unit viewer */
private final SearchBoxList<String> unitNameList;
@@ -194,11 +194,11 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
private final JTextArea unitTextBox;
/** The text box for prefix data in the prefix viewer */
private final JTextArea prefixTextBox;
-
+
// SETTINGS STUFF
private StandardRoundingType roundingType;
private int precision;
-
+
/**
* Creates the view and makes it visible to the user
*
@@ -215,161 +215,161 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
System.err.println("Failed to enable system look-and-feel.");
e.printStackTrace();
}
-
+
// initialize important components
this.presenter = new Presenter(this);
this.frame = new JFrame("7Units " + ProgramInfo.VERSION);
this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
-
+
// master components (those that contain everything else within them)
this.masterPane = new JTabbedPane();
this.frame.add(this.masterPane);
-
+
// ============ UNIT CONVERSION TAB ============
final JPanel convertUnitPanel = new JPanel();
this.masterPane.addTab("Convert Units", convertUnitPanel);
this.masterPane.setMnemonicAt(0, KeyEvent.VK_U);
convertUnitPanel.setLayout(new BorderLayout());
-
+
{ // panel for input part
final JPanel inputPanel = new JPanel();
convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
inputPanel.setLayout(new GridLayout(1, 3));
inputPanel.setBorder(new EmptyBorder(6, 6, 3, 6));
-
+
this.fromSearch = new SearchBoxList<>();
inputPanel.add(this.fromSearch);
-
+
final JPanel inBetweenPanel = new JPanel();
inputPanel.add(inBetweenPanel);
inBetweenPanel.setLayout(new BorderLayout());
-
+
this.dimensionSelector = new JComboBox<>();
inBetweenPanel.add(this.dimensionSelector, BorderLayout.PAGE_START);
this.dimensionSelector
.addItemListener(e -> this.presenter.updateView());
-
+
final JLabel arrowLabel = new JLabel("-->");
inBetweenPanel.add(arrowLabel, BorderLayout.CENTER);
arrowLabel.setHorizontalAlignment(SwingConstants.CENTER);
-
+
this.toSearch = new SearchBoxList<>();
inputPanel.add(this.toSearch);
}
-
+
{ // panel for submit and output, and also value entry
final JPanel outputPanel = new JPanel();
convertUnitPanel.add(outputPanel, BorderLayout.PAGE_END);
outputPanel.setLayout(new BorderLayout());
outputPanel.setBorder(new EmptyBorder(3, 6, 6, 6));
-
+
final JLabel valuePrompt = new JLabel("Value to convert: ");
outputPanel.add(valuePrompt, BorderLayout.LINE_START);
-
+
this.valueInput = new JTextField();
outputPanel.add(this.valueInput, BorderLayout.CENTER);
-
+
// conversion button
this.convertUnitButton = new JButton("Convert");
outputPanel.add(this.convertUnitButton, BorderLayout.LINE_END);
this.convertUnitButton
.addActionListener(e -> this.presenter.convertUnits());
this.convertUnitButton.setMnemonic(KeyEvent.VK_ENTER);
-
+
// conversion output
this.unitOutput = new JTextArea(2, 32);
outputPanel.add(this.unitOutput, BorderLayout.PAGE_END);
this.unitOutput.setEditable(false);
}
-
+
// ============ EXPRESSION CONVERSION TAB ============
final JPanel convertExpressionPanel = new JPanel();
this.masterPane.addTab("Convert Unit Expressions",
convertExpressionPanel);
this.masterPane.setMnemonicAt(1, KeyEvent.VK_E);
convertExpressionPanel.setLayout(new GridLayout(4, 1));
-
+
// from and to expressions
this.fromEntry = new JTextField();
convertExpressionPanel.add(this.fromEntry);
this.fromEntry.setBorder(BorderFactory.createTitledBorder("From"));
-
+
this.toEntry = new JTextField();
convertExpressionPanel.add(this.toEntry);
this.toEntry.setBorder(BorderFactory.createTitledBorder("To"));
-
+
// button to convert
this.convertExpressionButton = new JButton("Convert");
convertExpressionPanel.add(this.convertExpressionButton);
-
+
this.convertExpressionButton
.addActionListener(e -> this.presenter.convertExpressions());
this.convertExpressionButton.setMnemonic(KeyEvent.VK_ENTER);
-
+
// output of conversion
this.expressionOutput = new JTextArea(2, 32);
convertExpressionPanel.add(this.expressionOutput);
this.expressionOutput
.setBorder(BorderFactory.createTitledBorder("Output"));
this.expressionOutput.setEditable(false);
-
+
// =========== UNIT VIEWER ===========
final JPanel unitLookupPanel = new JPanel();
this.masterPane.addTab("Unit Viewer", unitLookupPanel);
this.masterPane.setMnemonicAt(2, KeyEvent.VK_V);
unitLookupPanel.setLayout(new GridLayout());
-
+
this.unitNameList = new SearchBoxList<>();
unitLookupPanel.add(this.unitNameList);
this.unitNameList.getSearchList()
.addListSelectionListener(e -> this.presenter.unitNameSelected());
-
+
// the text box for unit's toString
this.unitTextBox = new JTextArea();
unitLookupPanel.add(this.unitTextBox);
this.unitTextBox.setEditable(false);
this.unitTextBox.setLineWrap(true);
-
+
// ============ PREFIX VIEWER =============
final JPanel prefixLookupPanel = new JPanel();
this.masterPane.addTab("Prefix Viewer", prefixLookupPanel);
this.masterPane.setMnemonicAt(3, KeyEvent.VK_P);
prefixLookupPanel.setLayout(new GridLayout(1, 2));
-
+
this.prefixNameList = new SearchBoxList<>();
prefixLookupPanel.add(this.prefixNameList);
this.prefixNameList.getSearchList()
.addListSelectionListener(e -> this.presenter.prefixSelected());
-
+
// the text box for prefix's toString
this.prefixTextBox = new JTextArea();
prefixLookupPanel.add(this.prefixTextBox);
this.prefixTextBox.setEditable(false);
this.prefixTextBox.setLineWrap(true);
-
+
// ============ INFO PANEL ============
-
+
final JPanel infoPanel = new JPanel();
this.masterPane.addTab("\uD83D\uDEC8", // info (i) character
new JScrollPane(infoPanel));
-
+
final JTextArea infoTextArea = new JTextArea();
infoTextArea.setEditable(false);
infoTextArea.setOpaque(false);
infoPanel.add(infoTextArea);
infoTextArea.setText(Presenter.getAboutText());
-
+
// ============ SETTINGS PANEL ============
this.masterPane.addTab("\u2699",
new JScrollPane(this.createSettingsPanel()));
this.masterPane.setMnemonicAt(5, KeyEvent.VK_S);
-
+
// ============ FINALIZE CREATION OF VIEW ============
this.presenter.postViewInitialize();
this.frame.pack();
this.frame.setVisible(true);
}
-
+
/**
* Creates and returns the settings panel (in its own function to make this
* code more organized, as this function is massive!)
@@ -378,28 +378,28 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
*/
private JPanel createSettingsPanel() {
final JPanel settingsPanel = new JPanel();
-
+
settingsPanel
.setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
-
+
// ============ ROUNDING SETTINGS ============
{
final JPanel roundingPanel = new JPanel();
settingsPanel.add(roundingPanel);
roundingPanel.setBorder(new TitledBorder("Rounding Settings"));
roundingPanel.setLayout(new GridBagLayout());
-
+
// rounding rule selection
final ButtonGroup roundingRuleButtons = new ButtonGroup();
this.roundingType = this.getPresenterRoundingType()
.orElseThrow(() -> new AssertionError(
"Presenter loaded non-standard rounding rule"));
this.precision = this.getPresenterPrecision().orElse(6);
-
+
final JLabel roundingRuleLabel = new JLabel("Rounding Rule:");
roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
// sigDigSlider needs to be first so that the rounding-type buttons can
// show and hide it
final JLabel sliderLabel = new JLabel("Precision:");
@@ -407,26 +407,26 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.roundingType != StandardRoundingType.UNCERTAINTY);
roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JSlider sigDigSlider = new JSlider(0, 12);
roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
sigDigSlider.setMajorTickSpacing(4);
sigDigSlider.setMinorTickSpacing(1);
sigDigSlider.setSnapToTicks(true);
sigDigSlider.setPaintTicks(true);
sigDigSlider.setPaintLabels(true);
-
+
sigDigSlider.setVisible(
this.roundingType != StandardRoundingType.UNCERTAINTY);
sigDigSlider.setValue(this.precision);
-
+
sigDigSlider.addChangeListener(e -> {
this.precision = sigDigSlider.getValue();
this.updatePresenterRoundingRule();
});
-
+
// significant digit rounding
final JRadioButton fixedPrecision = new JRadioButton(
"Fixed Precision");
@@ -442,7 +442,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
roundingRuleButtons.add(fixedPrecision);
roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
// decimal place rounding
final JRadioButton fixedDecimals = new JRadioButton(
"Fixed Decimal Places");
@@ -458,7 +458,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
roundingRuleButtons.add(fixedDecimals);
roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
// scientific rounding
final JRadioButton relativePrecision = new JRadioButton(
"Uncertainty-Based Rounding");
@@ -475,7 +475,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3)
.setAnchor(GridBagConstraints.LINE_START).build());
}
-
+
// ============ PREFIX REPETITION SETTINGS ============
{
final JPanel prefixRepetitionPanel = new JPanel();
@@ -483,14 +483,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRepetitionPanel
.setBorder(new TitledBorder("Prefix Repetition Settings"));
prefixRepetitionPanel.setLayout(new GridBagLayout());
-
+
final var prefixRule = this.getPresenterPrefixRule()
.orElseThrow(() -> new AssertionError(
"Presenter loaded non-standard prefix rule"));
-
+
// prefix rules
final ButtonGroup prefixRuleButtons = new ButtonGroup();
-
+
final JRadioButton noRepetition = new JRadioButton("No Repetition");
if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) {
noRepetition.setSelected(true);
@@ -503,7 +503,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRuleButtons.add(noRepetition);
prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton noRestriction = new JRadioButton("No Restriction");
if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) {
noRestriction.setSelected(true);
@@ -516,7 +516,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRuleButtons.add(noRestriction);
prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton customRepetition = new JRadioButton(
"Complex Repetition");
if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) {
@@ -531,19 +531,19 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2)
.setAnchor(GridBagConstraints.LINE_START).build());
}
-
+
// ============ SEARCH SETTINGS ============
{
final JPanel searchingPanel = new JPanel();
settingsPanel.add(searchingPanel);
searchingPanel.setBorder(new TitledBorder("Search Settings"));
searchingPanel.setLayout(new GridBagLayout());
-
+
// searching rules
final ButtonGroup searchRuleButtons = new ButtonGroup();
-
+
final var searchRule = this.presenter.getSearchRule();
-
+
final JRadioButton noPrefixes = new JRadioButton(
"Never Include Prefixed Units");
noPrefixes.addActionListener(e -> {
@@ -554,7 +554,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
searchRuleButtons.add(noPrefixes);
searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton commonPrefixes = new JRadioButton(
"Include Common Prefixes");
commonPrefixes.addActionListener(e -> {
@@ -565,7 +565,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
searchRuleButtons.add(commonPrefixes);
searchingPanel.add(commonPrefixes, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton alwaysInclude = new JRadioButton(
"Include All Single Prefixes");
alwaysInclude.addActionListener(e -> {
@@ -577,7 +577,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
searchRuleButtons.add(alwaysInclude);
searchingPanel.add(alwaysInclude, new GridBagBuilder(0, 3)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) {
noPrefixes.setSelected(true);
} else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) {
@@ -589,13 +589,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.presenter.saveSettings();
}
}
-
+
// ============ OTHER SETTINGS ============
{
final JPanel miscPanel = new JPanel();
settingsPanel.add(miscPanel);
miscPanel.setLayout(new GridBagLayout());
-
+
final JCheckBox oneWay = new JCheckBox("Convert One Way Only");
oneWay.setSelected(this.presenter.oneWayConversionEnabled());
oneWay.addItemListener(e -> {
@@ -605,7 +605,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
});
miscPanel.add(oneWay, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JCheckBox showAllVariations = new JCheckBox(
"Show Duplicate Units & Prefixes");
showAllVariations.setSelected(this.presenter.duplicatesShown());
@@ -616,49 +616,49 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
});
miscPanel.add(showAllVariations, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JButton unitFileButton = new JButton("Manage Unit Data Files");
unitFileButton.setEnabled(false);
miscPanel.add(unitFileButton, new GridBagBuilder(0, 2)
.setAnchor(GridBagConstraints.LINE_START).build());
}
-
+
return settingsPanel;
}
-
+
@Override
public Set<String> getDimensionNames() {
return Collections
.unmodifiableSet(new JComboBoxItemSet<>(this.dimensionSelector));
}
-
+
@Override
public String getFromExpression() {
return this.fromEntry.getText();
}
-
+
@Override
public Optional<String> getFromSelection() {
return this.fromSearch.getSelectedValue();
}
-
+
@Override
public Set<String> getFromUnitNames() {
// this should work because the only way I can mutate the item list is
// with setFromUnits which only accepts a Set
return new HashSet<>(this.fromSearch.getItems());
}
-
+
@Override
public String getInputValue() {
return this.valueInput.getText();
}
-
+
@Override
public Presenter getPresenter() {
return this.presenter;
}
-
+
/**
* @return the precision of the presenter's rounding rule, if that is
* meaningful
@@ -678,7 +678,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
else
return OptionalInt.empty();
}
-
+
/**
* @return presenter's prefix repetition rule
* @since v0.4.0
@@ -690,7 +690,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
? Optional.of((DefaultPrefixRepetitionRule) prefixRule)
: Optional.empty();
}
-
+
/**
* Determines which rounding type the presenter is currently using, if any.
*
@@ -709,41 +709,41 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
else
return Optional.empty();
}
-
+
@Override
public Optional<String> getSelectedDimensionName() {
final String selectedItem = (String) this.dimensionSelector
.getSelectedItem();
return Optional.ofNullable(selectedItem);
}
-
+
@Override
public String getToExpression() {
return this.toEntry.getText();
}
-
+
@Override
public Optional<String> getToSelection() {
return this.toSearch.getSelectedValue();
}
-
+
@Override
public Set<String> getToUnitNames() {
// this should work because the only way I can mutate the item list is
// with setToUnits which only accepts a Set
return new HashSet<>(this.toSearch.getItems());
}
-
+
@Override
public Optional<String> getViewedPrefixName() {
return this.prefixNameList.getSelectedValue();
}
-
+
@Override
public Optional<String> getViewedUnitName() {
return this.unitNameList.getSelectedValue();
}
-
+
@Override
public void setDimensionNames(Set<String> dimensionNames) {
this.dimensionSelector.removeAllItems();
@@ -751,45 +751,45 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.dimensionSelector.addItem(d);
}
}
-
+
@Override
public void setFromUnitNames(Set<String> units) {
this.fromSearch.setItems(units);
}
-
+
@Override
public void setToUnitNames(Set<String> units) {
this.toSearch.setItems(units);
}
-
+
@Override
public void setViewablePrefixNames(Set<String> prefixNames) {
this.prefixNameList.setItems(prefixNames);
}
-
+
@Override
public void setViewableUnitNames(Set<String> unitNames) {
this.unitNameList.setItems(unitNames);
}
-
+
@Override
public void showErrorMessage(String title, String message) {
JOptionPane.showMessageDialog(this.frame, message, title,
JOptionPane.ERROR_MESSAGE);
}
-
+
@Override
public void showExpressionConversionOutput(UnitConversionRecord uc) {
this.expressionOutput.setText(String.format("%s = %s %s", uc.fromName(),
uc.outputValueString(), uc.toName()));
}
-
+
@Override
public void showPrefix(NameSymbol name, String multiplierString) {
this.prefixTextBox.setText(
String.format("%s%nMultiplier: %s", name, multiplierString));
}
-
+
@Override
public void showUnit(NameSymbol name, String definition,
String dimensionName, UnitType type) {
@@ -797,12 +797,12 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
String.format("%s%nDefinition: %s%nDimension: %s%nType: %s", name,
definition, dimensionName, type));
}
-
+
@Override
public void showUnitConversionOutput(UnitConversionRecord uc) {
this.unitOutput.setText(uc.toString());
}
-
+
/**
* Sets the presenter's rounding rule to the one specified by the current
* settings
diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
index fa64ee9..43a62e6 100644
--- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
+++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
@@ -44,7 +44,7 @@ public final class UnitConversionRecord {
input.getValue().toString(false, RoundingMode.HALF_EVEN),
output.getValue().toString(false, RoundingMode.HALF_EVEN));
}
-
+
/**
* Gets a {@code UnitConversionRecord} from two unit values
*
@@ -60,7 +60,7 @@ public final class UnitConversionRecord {
output.getUnit().getName(), String.valueOf(input.getValue()),
String.valueOf(output.getValue()));
}
-
+
/**
* Gets a {@code UnitConversionRecord}
*
@@ -78,7 +78,7 @@ public final class UnitConversionRecord {
return new UnitConversionRecord(fromName, toName, inputValueString,
outputValueString);
}
-
+
/**
* The name of the unit or expression that was converted from
*/
@@ -87,7 +87,7 @@ public final class UnitConversionRecord {
* The name of the unit or expression that was converted to
*/
private final String toName;
-
+
/**
* A string representing the input value. It doesn't need to be the same as
* the input value's string representation; it could be rounded, for example.
@@ -98,7 +98,7 @@ public final class UnitConversionRecord {
* the input value's string representation; it could be rounded, for example.
*/
private final String outputValueString;
-
+
/**
* @param fromName name of unit or expression that was converted
* from
@@ -114,7 +114,7 @@ public final class UnitConversionRecord {
this.inputValueString = inputValueString;
this.outputValueString = outputValueString;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -144,7 +144,7 @@ public final class UnitConversionRecord {
return false;
return true;
}
-
+
/**
* @return name of unit or expression that was converted from
* @since v0.4.0
@@ -153,7 +153,7 @@ public final class UnitConversionRecord {
public String fromName() {
return this.fromName;
}
-
+
@Override
public int hashCode() {
final int prime = 31;
@@ -168,7 +168,7 @@ public final class UnitConversionRecord {
+ (this.toName == null ? 0 : this.toName.hashCode());
return result;
}
-
+
/**
* @return string representing input value
* @since v0.4.0
@@ -177,7 +177,7 @@ public final class UnitConversionRecord {
public String inputValueString() {
return this.inputValueString;
}
-
+
/**
* @return string representing output value
* @since v0.4.0
@@ -186,7 +186,7 @@ public final class UnitConversionRecord {
public String outputValueString() {
return this.outputValueString;
}
-
+
/**
* @return name of unit or expression that was converted to
* @since v0.4.0
@@ -195,7 +195,7 @@ public final class UnitConversionRecord {
public String toName() {
return this.toName;
}
-
+
@Override
public String toString() {
final String inputString = this.inputValueString.isBlank() ? this.fromName
diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java
index 0d07823..b9077f7 100644
--- a/src/main/java/sevenUnitsGUI/UnitConversionView.java
+++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java
@@ -33,21 +33,21 @@ public interface UnitConversionView extends View {
* @since 2022-01-29
*/
Set<String> getDimensionNames();
-
+
/**
* @return name of unit to convert <em>from</em>
* @since v0.4.0
* @since 2021-12-15
*/
Optional<String> getFromSelection();
-
+
/**
* @return list of names of units available to convert from
* @since v0.4.0
* @since 2022-03-30
*/
Set<String> getFromUnitNames();
-
+
/**
* @return value to convert between the units (specifically, the numeric
* string provided by the user)
@@ -55,28 +55,28 @@ public interface UnitConversionView extends View {
* @since 2021-12-15
*/
String getInputValue();
-
+
/**
* @return selected dimension
* @since v0.4.0
* @since 2021-12-15
*/
Optional<String> getSelectedDimensionName();
-
+
/**
* @return name of unit to convert <em>to</em>
* @since v0.4.0
* @since 2021-12-15
*/
Optional<String> getToSelection();
-
+
/**
* @return list of names of units available to convert to
* @since v0.4.0
* @since 2022-03-30
*/
Set<String> getToUnitNames();
-
+
/**
* Sets the available dimensions for filtering.
*
@@ -85,7 +85,7 @@ public interface UnitConversionView extends View {
* @since 2021-12-15
*/
void setDimensionNames(Set<String> dimensionNames);
-
+
/**
* Sets the available units to convert from. {@link #getFromSelection} is not
* required to use one of these units; this method is to be used for views
@@ -96,7 +96,7 @@ public interface UnitConversionView extends View {
* @since 2021-12-15
*/
void setFromUnitNames(Set<String> unitNames);
-
+
/**
* Sets the available units to convert to. {@link #getToSelection} is not
* required to use one of these units; this method is to be used for views
@@ -107,7 +107,7 @@ public interface UnitConversionView extends View {
* @since 2021-12-15
*/
void setToUnitNames(Set<String> unitNames);
-
+
/**
* Shows the output of a unit conversion.
*
diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java
index bb810ec..7dd0c44 100644
--- a/src/main/java/sevenUnitsGUI/View.java
+++ b/src/main/java/sevenUnitsGUI/View.java
@@ -38,28 +38,28 @@ public interface View {
static View createTabbedView() {
return new TabbedView();
}
-
+
/**
* @return the presenter associated with this view
* @since v0.4.0
* @since 2022-04-19
*/
Presenter getPresenter();
-
+
/**
* @return name of prefix currently being viewed
* @since v0.4.0
* @since 2022-04-10
*/
Optional<String> getViewedPrefixName();
-
+
/**
* @return name of unit currently being viewed
* @since v0.4.0
* @since 2022-04-10
*/
Optional<String> getViewedUnitName();
-
+
/**
* Sets the list of prefixes that are available to be viewed in a prefix
* viewer
@@ -69,7 +69,7 @@ public interface View {
* @since 2022-04-10
*/
void setViewablePrefixNames(Set<String> prefixNames);
-
+
/**
* Sets the list of units that are available to be viewed in a unit viewer
*
@@ -78,7 +78,7 @@ public interface View {
* @since 2022-04-10
*/
void setViewableUnitNames(Set<String> unitNames);
-
+
/**
* Shows an error message.
*
@@ -89,7 +89,7 @@ public interface View {
* @since 2021-12-15
*/
void showErrorMessage(String title, String message);
-
+
/**
* Shows information about a prefix to the user.
*
@@ -99,7 +99,7 @@ public interface View {
* @since 2022-04-10
*/
void showPrefix(NameSymbol name, String multiplierString);
-
+
/**
* Shows information about a unit to the user.
*
diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java
index e7304c4..e6593fb 100644
--- a/src/main/java/sevenUnitsGUI/ViewBot.java
+++ b/src/main/java/sevenUnitsGUI/ViewBot.java
@@ -46,7 +46,7 @@ public final class ViewBot
public static final class PrefixViewingRecord implements Nameable {
private final NameSymbol nameSymbol;
private final String multiplierString;
-
+
/**
* @param nameSymbol
* @param multiplierString
@@ -57,7 +57,7 @@ public final class ViewBot
this.nameSymbol = nameSymbol;
this.multiplierString = multiplierString;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -68,25 +68,25 @@ public final class ViewBot
return Objects.equals(this.multiplierString, other.multiplierString)
&& Objects.equals(this.nameSymbol, other.nameSymbol);
}
-
+
@Override
public NameSymbol getNameSymbol() {
return this.nameSymbol;
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.multiplierString, this.nameSymbol);
}
-
+
public String multiplierString() {
return this.multiplierString;
}
-
+
public NameSymbol nameSymbol() {
return this.nameSymbol;
}
-
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@@ -98,7 +98,7 @@ public final class ViewBot
return builder.toString();
}
}
-
+
/**
* A record of the parameters given to
* {@link View#showUnit(NameSymbol, String, String, UnitType)}, for testing.
@@ -110,7 +110,7 @@ public final class ViewBot
private final String definition;
private final String dimensionName;
private final UnitType unitType;
-
+
/**
* @since 2022-04-16
*/
@@ -121,7 +121,7 @@ public final class ViewBot
this.dimensionName = dimensionName;
this.unitType = unitType;
}
-
+
/**
* @return the definition
* @since 2022-04-16
@@ -129,7 +129,7 @@ public final class ViewBot
public String definition() {
return this.definition;
}
-
+
/**
* @return the dimensionName
* @since 2022-04-16
@@ -137,7 +137,7 @@ public final class ViewBot
public String dimensionName() {
return this.dimensionName;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -150,7 +150,7 @@ public final class ViewBot
&& Objects.equals(this.nameSymbol, other.nameSymbol)
&& this.unitType == other.unitType;
}
-
+
/**
* @return the nameSymbol
* @since 2022-04-16
@@ -159,17 +159,17 @@ public final class ViewBot
public NameSymbol getNameSymbol() {
return this.nameSymbol;
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.definition, this.dimensionName,
this.nameSymbol, this.unitType);
}
-
+
public NameSymbol nameSymbol() {
return this.nameSymbol;
}
-
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@@ -184,7 +184,7 @@ public final class ViewBot
builder.append("]");
return builder.toString();
}
-
+
/**
* @return the unitType
* @since 2022-04-16
@@ -193,10 +193,10 @@ public final class ViewBot
return this.unitType;
}
}
-
+
/** The presenter that works with this ViewBot */
private final Presenter presenter;
-
+
/** The dimensions available to select from */
private Set<String> dimensionNames = Set.of();
/** The expression in the From field */
@@ -217,12 +217,12 @@ public final class ViewBot
private Set<String> fromUnits = Set.of();
/** The units available in the To selection */
private Set<String> toUnits = Set.of();
-
+
/** The selected unit in the unit viewer */
private Optional<String> unitViewerSelection = Optional.empty();
/** The selected unit in the prefix viewer */
private Optional<String> prefixViewerSelection = Optional.empty();
-
+
/** Saved outputs of all unit conversions */
private final List<UnitConversionRecord> unitConversions;
/** Saved outputs of all unit expressions */
@@ -231,7 +231,7 @@ public final class ViewBot
private final List<UnitViewingRecord> unitViewingRecords;
/** Saved outputs of all prefix viewings */
private final List<PrefixViewingRecord> prefixViewingRecords;
-
+
/**
* Creates a new {@code ViewBot} with a new presenter.
*
@@ -239,13 +239,13 @@ public final class ViewBot
*/
public ViewBot() {
this.presenter = new Presenter(this);
-
+
this.unitConversions = new ArrayList<>();
this.expressionConversions = new ArrayList<>();
this.unitViewingRecords = new ArrayList<>();
this.prefixViewingRecords = new ArrayList<>();
}
-
+
/**
* @return list of records of expression conversions done by this bot
* @since 2022-04-09
@@ -253,7 +253,7 @@ public final class ViewBot
public List<UnitConversionRecord> expressionConversionList() {
return Collections.unmodifiableList(this.expressionConversions);
}
-
+
/**
* @return the available dimensions
* @since 2022-01-29
@@ -262,17 +262,17 @@ public final class ViewBot
public Set<String> getDimensionNames() {
return this.dimensionNames;
}
-
+
@Override
public String getFromExpression() {
return this.fromExpression;
}
-
+
@Override
public Optional<String> getFromSelection() {
return this.fromSelection;
}
-
+
/**
* @return the units available for selection in From
* @since 2022-01-29
@@ -281,12 +281,12 @@ public final class ViewBot
public Set<String> getFromUnitNames() {
return Collections.unmodifiableSet(this.fromUnits);
}
-
+
@Override
public String getInputValue() {
return this.inputValue;
}
-
+
/**
* @return the presenter associated with tihs view
* @since 2022-01-29
@@ -295,22 +295,22 @@ public final class ViewBot
public Presenter getPresenter() {
return this.presenter;
}
-
+
@Override
public Optional<String> getSelectedDimensionName() {
return this.selectedDimensionName;
}
-
+
@Override
public String getToExpression() {
return this.toExpression;
}
-
+
@Override
public Optional<String> getToSelection() {
return this.toSelection;
}
-
+
/**
* @return the units available for selection in To
* @since 2022-01-29
@@ -319,17 +319,17 @@ public final class ViewBot
public Set<String> getToUnitNames() {
return Collections.unmodifiableSet(this.toUnits);
}
-
+
@Override
public Optional<String> getViewedPrefixName() {
return this.prefixViewerSelection;
}
-
+
@Override
public Optional<String> getViewedUnitName() {
return this.unitViewerSelection;
}
-
+
/**
* @return list of records of this viewBot's prefix views
* @since 2022-04-16
@@ -337,13 +337,13 @@ public final class ViewBot
public List<PrefixViewingRecord> prefixViewList() {
return Collections.unmodifiableList(this.prefixViewingRecords);
}
-
+
@Override
public void setDimensionNames(Set<String> dimensionNames) {
this.dimensionNames = Objects.requireNonNull(dimensionNames,
"dimensions may not be null");
}
-
+
/**
* Sets the From expression (as in {@link #getFromExpression}).
*
@@ -355,7 +355,7 @@ public final class ViewBot
this.fromExpression = Objects.requireNonNull(fromExpression,
"fromExpression cannot be null.");
}
-
+
/**
* @param fromSelection the fromSelection to set
* @since 2022-01-29
@@ -364,7 +364,7 @@ public final class ViewBot
this.fromSelection = Objects.requireNonNull(fromSelection,
"fromSelection cannot be null");
}
-
+
/**
* @param fromSelection the fromSelection to set
* @since 2022-02-10
@@ -372,12 +372,12 @@ public final class ViewBot
public void setFromSelection(String fromSelection) {
this.setFromSelection(Optional.of(fromSelection));
}
-
+
@Override
public void setFromUnitNames(Set<String> units) {
this.fromUnits = Objects.requireNonNull(units, "units may not be null");
}
-
+
/**
* @param inputValue the inputValue to set
* @since 2022-01-29
@@ -385,7 +385,7 @@ public final class ViewBot
public void setInputValue(String inputValue) {
this.inputValue = inputValue;
}
-
+
/**
* @param selectedDimension the selectedDimension to set
* @since 2022-01-29
@@ -394,11 +394,11 @@ public final class ViewBot
Optional<String> selectedDimensionName) {
this.selectedDimensionName = selectedDimensionName;
}
-
+
public void setSelectedDimensionName(String selectedDimensionName) {
this.setSelectedDimensionName(Optional.of(selectedDimensionName));
}
-
+
/**
* Sets the To expression (as in {@link #getToExpression}).
*
@@ -410,7 +410,7 @@ public final class ViewBot
this.toExpression = Objects.requireNonNull(toExpression,
"toExpression cannot be null.");
}
-
+
/**
* @param toSelection the toSelection to set
* @since 2022-01-29
@@ -419,77 +419,77 @@ public final class ViewBot
this.toSelection = Objects.requireNonNull(toSelection,
"toSelection cannot be null.");
}
-
+
public void setToSelection(String toSelection) {
this.setToSelection(Optional.of(toSelection));
}
-
+
@Override
public void setToUnitNames(Set<String> units) {
this.toUnits = Objects.requireNonNull(units, "units may not be null");
}
-
+
@Override
public void setViewablePrefixNames(Set<String> prefixNames) {
// do nothing, ViewBot supports selecting any prefix
}
-
+
@Override
public void setViewableUnitNames(Set<String> unitNames) {
// do nothing, ViewBot supports selecting any unit
}
-
+
public void setViewedPrefixName(Optional<String> viewedPrefixName) {
this.prefixViewerSelection = viewedPrefixName;
}
-
+
public void setViewedPrefixName(String viewedPrefixName) {
this.setViewedPrefixName(Optional.of(viewedPrefixName));
}
-
+
public void setViewedUnitName(Optional<String> viewedUnitName) {
this.unitViewerSelection = viewedUnitName;
}
-
+
public void setViewedUnitName(String viewedUnitName) {
this.setViewedUnitName(Optional.of(viewedUnitName));
}
-
+
@Override
public void showErrorMessage(String title, String message) {
System.err.printf("%s: %s%n", title, message);
}
-
+
@Override
public void showExpressionConversionOutput(UnitConversionRecord uc) {
this.expressionConversions.add(uc);
System.out.println("Expression Conversion: " + uc);
}
-
+
@Override
public void showPrefix(NameSymbol name, String multiplierString) {
this.prefixViewingRecords
.add(new PrefixViewingRecord(name, multiplierString));
}
-
+
@Override
public void showUnit(NameSymbol name, String definition,
String dimensionName, UnitType type) {
this.unitViewingRecords
.add(new UnitViewingRecord(name, definition, dimensionName, type));
}
-
+
@Override
public void showUnitConversionOutput(UnitConversionRecord uc) {
this.unitConversions.add(uc);
System.out.println("Unit Conversion: " + uc);
}
-
+
@Override
public String toString() {
return super.toString() + String.format("[presenter=%s]", this.presenter);
}
-
+
/**
* @return list of records of every unit conversion made by this bot
* @since 2022-04-09
@@ -497,7 +497,7 @@ public final class ViewBot
public List<UnitConversionRecord> unitConversionList() {
return Collections.unmodifiableList(this.unitConversions);
}
-
+
/**
* @return list of records of unit viewings made by this bot
* @since 2022-04-16
diff --git a/src/main/resources/about.txt b/src/main/resources/about.txt
index 5cdcf67..782b422 100644
--- a/src/main/resources/about.txt
+++ b/src/main/resources/about.txt
@@ -1,8 +1,17 @@
About 7Units Version [VERSION]
+7Units is a unit converter program with many features,
+inspired by GNU Units (https://www.gnu.org/software/units/).
+You can use it to simply convert units, but you can also
+use it like a calculator, computing and converting expressions
+like "10 m/s + (25^2 - 5^2) mi/hr".
+
+This software was written by Adrien Hopkins
+<adrien.p.hopkins@gmail.com>.
+
Copyright Notice:
-Unit Converter Copyright (C) 2018-2023 Adrien Hopkins
+Unit Converter Copyright (C) 2018-2024 Adrien Hopkins
This program comes with ABSOLUTELY NO WARRANTY;
for details read the LICENSE file, section 15
diff --git a/src/test/java/sevenUnits/unit/MultiUnitTest.java b/src/test/java/sevenUnits/unit/MultiUnitTest.java
index 30f2941..949a1f1 100644
--- a/src/test/java/sevenUnits/unit/MultiUnitTest.java
+++ b/src/test/java/sevenUnits/unit/MultiUnitTest.java
@@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test;
* @since 2020-10-03
*/
class MultiUnitTest {
-
+
/**
* Ensures that the {@code MultiUnit} can convert properly.
*/
@@ -40,24 +40,24 @@ class MultiUnitTest {
final Random rng = ThreadLocalRandom.current();
final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT,
BritishImperial.Length.INCH);
-
+
assertEquals(1702.0,
footInch.convertTo(Metric.METRE.withPrefix(Metric.MILLI),
Arrays.asList(5.0, 7.0)),
1.0);
-
+
for (int i = 0; i < 1000; i++) {
final double feet = rng.nextInt(1000);
final double inches = rng.nextDouble() * 12;
final double millimetres = feet * 304.8 + inches * 25.4;
-
+
final List<Double> feetAndInches = Metric.METRE
.withPrefix(Metric.MILLI).convertTo(footInch, millimetres);
assertEquals(feet, feetAndInches.get(0), 1e-10);
assertEquals(inches, feetAndInches.get(1), 1e-10);
}
}
-
+
/**
* Test method for {@link sevenUnits.unit.MultiUnit#convertFromBase(double)}.
*/
@@ -66,24 +66,24 @@ class MultiUnitTest {
final Random rng = ThreadLocalRandom.current();
final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT,
BritishImperial.Length.INCH);
-
+
// 1.7 m =~ 5' + 7"
final List<Double> values = footInch.convertFromBase(1.7018);
-
+
assertEquals(5, values.get(0));
assertEquals(7, values.get(1), 1e-12);
-
+
for (int i = 0; i < 1000; i++) {
final double feet = rng.nextInt(1000);
final double inches = rng.nextDouble() * 12;
final double metres = feet * 0.3048 + inches * 0.0254;
-
+
final List<Double> feetAndInches = footInch.convertFromBase(metres);
assertEquals(feet, feetAndInches.get(0), 1e-10);
assertEquals(inches, feetAndInches.get(1), 1e-10);
}
}
-
+
/**
* Test method for
* {@link sevenUnits.unit.MultiUnit#convertToBase(java.util.List)}.
@@ -93,16 +93,16 @@ class MultiUnitTest {
final Random rng = ThreadLocalRandom.current();
final MultiUnit footInch = MultiUnit.of(BritishImperial.Length.FOOT,
BritishImperial.Length.INCH);
-
+
// 1.7 m =~ 5' + 7"
assertEquals(1.7018, footInch.convertToBase(Arrays.asList(5.0, 7.0)),
1e-12);
-
+
for (int i = 0; i < 1000; i++) {
final double feet = rng.nextInt(1000);
final double inches = rng.nextDouble() * 12;
final double metres = feet * 0.3048 + inches * 0.0254;
-
+
assertEquals(metres,
footInch.convertToBase(Arrays.asList(feet, inches)), 1e-12);
}
diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java
index 4be33dd..9d650f0 100644
--- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java
+++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java
@@ -53,9 +53,9 @@ import sevenUnits.utils.UncertainDouble;
class UnitDatabaseTest {
private static final class SimpleEntry<K, V> implements Map.Entry<K, V> {
private final K key;
-
+
private V value;
-
+
/**
*
* @since 2021-10-07
@@ -64,7 +64,7 @@ class UnitDatabaseTest {
this.key = key;
this.value = value;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -75,23 +75,23 @@ class UnitDatabaseTest {
return Objects.equals(this.key, other.getKey())
&& Objects.equals(this.value, other.getValue());
}
-
+
@Override
public K getKey() {
return this.key;
}
-
+
@Override
public V getValue() {
return this.value;
}
-
+
@Override
public int hashCode() {
return (this.key == null ? 0 : this.key.hashCode())
^ (this.value == null ? 0 : this.value.hashCode());
}
-
+
@Override
public V setValue(V value) {
final V oldValue = this.value;
@@ -99,20 +99,20 @@ class UnitDatabaseTest {
return oldValue;
}
}
-
+
// some linear units and one nonlinear
private static final Unit U = Metric.METRE;
private static final Unit V = Metric.KILOGRAM;
-
+
private static final Unit W = Metric.SECOND;
// used for testing expressions
// J = U^2 * V / W^2
private static final LinearUnit J = Metric.KILOGRAM
.times(Metric.METRE.toExponent(2))
.dividedBy(Metric.SECOND.toExponent(2));
-
+
private static final LinearUnit K = Metric.KELVIN;
-
+
private static final Unit NONLINEAR = Unit.fromConversionFunctions(
Metric.METRE.getBase(), o -> o + 1, o -> o - 1);
// make the prefix values prime so I can tell which multiplications were made
@@ -123,9 +123,9 @@ class UnitDatabaseTest {
private static final UnitPrefix C = UnitPrefix.valueOf(5)
.withName(NameSymbol.ofName("C"));
private static final UnitPrefix AB = UnitPrefix.valueOf(7);
-
+
private static final UnitPrefix BC = UnitPrefix.valueOf(11);
-
+
/**
* Gets a map entry.
*
@@ -139,7 +139,7 @@ class UnitDatabaseTest {
private static <K, V> Map.Entry<K, V> entry(K key, V value) {
return new SimpleEntry<>(key, value);
}
-
+
/**
* Loads the dimensionfile at src/test/resources/[path] to the database
* {@code loadTo}.
@@ -156,7 +156,7 @@ class UnitDatabaseTest {
fail(e.getClass() + " occurred upon loading file \"" + path + "\".");
}
}
-
+
/**
* Loads the unitfile at src/test/resources/[path] to the database
* {@code loadTo}.
@@ -173,7 +173,7 @@ class UnitDatabaseTest {
fail(e.getClass() + " occurred upon loading file \"" + path + "\".");
}
}
-
+
/**
* A test for the {@link UnitDatabase#evaluateUnitExpression(String)}
* function. Simple because the expression parser has its own test.
@@ -183,26 +183,26 @@ class UnitDatabaseTest {
@Test
public void testEvaluateExpression() {
final UnitDatabase database = new UnitDatabase();
-
+
database.addUnit("J", J);
database.addUnit("K", K);
-
+
database.addPrefix("A", A);
database.addPrefix("B", B);
database.addPrefix("C", C);
-
+
final LinearUnitValue expected = LinearUnitValue.of(J,
UncertainDouble.of(12, Math.sqrt(14.625)));
// note: units are exact, each number has an uncertainty of 1
final LinearUnitValue actual = database
.evaluateUnitExpression("J + (2 * 3) J + (20 / 4) J");
assertEquals(expected, actual);
-
+
// check that negation works properly
assertEquals(2,
database.evaluateUnitExpression("J - -1 * J").getValueExact());
}
-
+
/**
* Test for {@link UnitDatabase#getUnit}, {@link UnitDatabase#getLinearUnit}
* and {@link UnitDatabase#getLinearUnitValue}.
@@ -212,14 +212,14 @@ class UnitDatabaseTest {
@Test
public void testGetUnit() {
final UnitDatabase database = new UnitDatabase();
-
+
database.addUnit("m", Metric.METRE);
database.addUnit("meter", Metric.METRE);
database.addUnit("metre", Metric.METRE);
database.addUnit("badname", Metric.METRE);
database.addUnit("K", Metric.KELVIN);
database.addUnit("degC", Metric.CELSIUS);
-
+
// ensure getUnit returns units, regardless of whether the name is one of
// the unit's names
assertEquals(Metric.METRE, database.getUnit("m"));
@@ -228,14 +228,14 @@ class UnitDatabaseTest {
assertEquals(Metric.METRE, database.getUnit("badname"));
assertThrows(NoSuchElementException.class,
() -> database.getUnit("blabla"));
-
+
assertEquals(Metric.KELVIN, database.getLinearUnit("K"));
assertThrows(IllegalArgumentException.class,
() -> database.getLinearUnit("degC"));
assertEquals(Metric.KELVIN.times(373.15),
database.getLinearUnit("degC(100)"));
}
-
+
/**
* Confirms that operations that shouldn't function for infinite databases
* throw an {@code IllegalStateException}.
@@ -247,21 +247,21 @@ class UnitDatabaseTest {
public void testInfiniteSetExceptions() {
// load units
final UnitDatabase infiniteDatabase = new UnitDatabase();
-
+
infiniteDatabase.addUnit("J", J);
infiniteDatabase.addUnit("K", K);
-
+
infiniteDatabase.addPrefix("A", A);
infiniteDatabase.addPrefix("B", B);
infiniteDatabase.addPrefix("C", C);
-
+
final Set<Entry<String, Unit>> entrySet = infiniteDatabase.unitMap()
.entrySet();
final Set<String> keySet = infiniteDatabase.unitMap().keySet();
assertThrows(IllegalStateException.class, () -> entrySet.toArray());
assertThrows(IllegalStateException.class, () -> keySet.toArray());
}
-
+
/**
* A bunch of tests for invalid dimension files
*
@@ -282,7 +282,7 @@ class UnitDatabaseTest {
assertTrue(e instanceof IllegalArgumentException
|| e instanceof NoSuchElementException);
}
-
+
/**
* A bunch of tests for invalid unit files
*
@@ -300,7 +300,7 @@ class UnitDatabaseTest {
assertTrue(e instanceof IllegalArgumentException
|| e instanceof NoSuchElementException);
}
-
+
/**
* Tests loading a valid dimension-file with some derived dimensions.
*
@@ -312,13 +312,13 @@ class UnitDatabaseTest {
database.addDimension("LENGTH", Metric.Dimensions.LENGTH);
database.addDimension("MASS", Metric.Dimensions.MASS);
database.addDimension("TIME", Metric.Dimensions.TIME);
-
+
loadDimensionFile(database, "/test-dimensionfile-valid1.txt");
assertEquals(Metric.Dimensions.ENERGY, database.getDimension("ENERGY"));
assertEquals(Metric.Dimensions.POWER, database.getDimension("POWER"));
-
+
}
-
+
/**
* Tests loading a valid unitfile with some prefixes and no units.
*
@@ -327,13 +327,13 @@ class UnitDatabaseTest {
@Test
public void testLoadingValidPrefixes() {
final UnitDatabase database = new UnitDatabase();
-
+
loadUnitsFile(database, "/test-unitsfile-valid2.txt");
assertEquals(7, database.getPrefix("A").getMultiplier());
assertEquals(11, database.getPrefix("B").getMultiplier());
assertEquals(13, database.getPrefix("C").getMultiplier());
}
-
+
/**
* Tests loading a valid unitfile with some units and preloaded prefixes
*
@@ -342,43 +342,43 @@ class UnitDatabaseTest {
@Test
public void testLoadingValidUnits() {
final UnitDatabase database = new UnitDatabase();
-
+
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);
-
+
loadUnitsFile(database, "/test-unitsfile-valid1.txt");
-
+
final Unit expected1 = ((LinearUnit) U).withPrefix(A).withPrefix(B)
.withPrefix(C);
final Unit actual1 = database.getUnit("test1");
assertEquals(expected1, actual1);
-
+
final Unit expected2 = ((LinearUnit) W).withPrefix(B)
.times(((LinearUnit) V).withPrefix(C));
final Unit actual2 = database.getUnit("test2");
assertEquals(expected2, actual2);
-
+
final Unit expected3 = ((LinearUnit) U)
.times(A.getMultiplier() + C.getMultiplier() - B.getMultiplier());
final Unit actual3 = database.getUnit("test3");
assertEquals(expected3, actual3);
-
+
final UnitValue expected4 = UnitValue.of(U, 1);
final UnitValue actual4 = database
.evaluateUnitExpression("-5 * U + -3 * U + 12 * U - 3 * U")
.asUnitValue();
assertEquals(expected4, actual4);
-
+
assertTrue(System.err.toString().length() > 0);
}
-
+
/**
* Tests the iterator of the prefixless unit map. These tests are simple, as
* the unit map iterator is simple.
@@ -388,16 +388,16 @@ class UnitDatabaseTest {
@Test
public void testPrefixedUnitMapIterator() {
final UnitDatabase database1 = new UnitDatabase();
-
+
database1.addUnit("U", U);
database1.addUnit("V", V);
database1.addUnit("W", W);
-
+
final Map<String, Unit> map1 = database1.unitMap();
final Iterator<String> keyIterator1 = map1.keySet().iterator();
final Iterator<Map.Entry<String, Unit>> entryIterator1 = map1.entrySet()
.iterator();
-
+
final Set<String> expectedKeys = Set.of("U", "V", "W");
final Set<String> actualKeys = new HashSet<>();
while (keyIterator1.hasNext()) {
@@ -405,7 +405,7 @@ class UnitDatabaseTest {
}
assertEquals(expectedKeys, actualKeys);
assertEquals(expectedKeys, map1.keySet());
-
+
final Set<Map.Entry<String, Unit>> expectedEntries = Set.of(entry("U", U),
entry("V", V), entry("W", W));
final Set<Map.Entry<String, Unit>> actualEntries = new HashSet<>();
@@ -415,7 +415,7 @@ class UnitDatabaseTest {
assertEquals(expectedEntries, actualEntries);
assertEquals(expectedEntries, map1.entrySet());
}
-
+
/**
* Test that prefixes correctly apply to units.
*
@@ -425,28 +425,28 @@ class UnitDatabaseTest {
@Test
public void testPrefixes() {
final UnitDatabase database = new UnitDatabase();
-
+
database.addUnit("U", U);
database.addUnit("V", V);
database.addUnit("W", W);
-
+
database.addPrefix("A", A);
database.addPrefix("B", B);
database.addPrefix("C", C);
-
+
// test the getPrefixesFromName method
final List<UnitPrefix> expected = Arrays.asList(C, B, A);
assertEquals(expected, database.getPrefixesFromName("ABCU"));
-
+
// 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.
*
@@ -462,19 +462,19 @@ class UnitDatabaseTest {
final UnitDatabase database = new UnitDatabase();
final Map<String, Unit> prefixlessUnits = database
.unitMapPrefixless(true);
-
+
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.
@@ -485,18 +485,18 @@ class UnitDatabaseTest {
@Test
public void testPrefixlessUnits() {
final UnitDatabase database = new UnitDatabase();
-
+
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"));
assertThrows(NoSuchElementException.class, () -> database.getUnit("Z"));
}
-
+
@Test
public void testRemovableDuplicates() {
final Map<String, Unit> unitMap = new HashMap<>();
@@ -504,7 +504,7 @@ class UnitDatabaseTest {
unitMap.put("metre", Metric.METRE);
unitMap.put("m", Metric.METRE);
unitMap.put("second", Metric.SECOND);
-
+
assertTrue(UnitDatabase.isRemovableDuplicate(unitMap,
entry("m", Metric.METRE)));
assertTrue(UnitDatabase.isRemovableDuplicate(unitMap,
@@ -514,28 +514,28 @@ class UnitDatabaseTest {
assertFalse(UnitDatabase.isRemovableDuplicate(unitMap,
entry("second", Metric.SECOND)));
}
-
+
@Test
public void testToString() {
final UnitDatabase database = new UnitDatabase();
-
+
database.addUnit("J", J);
database.addUnit("K", J);
-
+
database.addPrefix("A", A);
database.addPrefix("B", B);
database.addPrefix("C", C);
-
+
if ("Unit Database with 1 units, 3 unit prefixes and 0 dimensions"
.equals(database.toString())) {
fail("Database counts by number of units, not number of unit names.");
}
-
+
assertEquals(
"Unit Database with 2 units, 3 unit prefixes and 0 dimensions",
database.toString());
}
-
+
/**
* Test that unit expressions return the correct value.
*
@@ -546,37 +546,37 @@ class UnitDatabaseTest {
public void testUnitExpressions() {
// load units
final UnitDatabase database = new UnitDatabase();
-
+
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);
-
+
// test incorrect expressions
assertThrows(IllegalArgumentException.class,
() -> database.getUnitFromExpression("U + V"));
assertThrows(IllegalArgumentException.class,
() -> database.getUnitFromExpression("U - V"));
}
-
+
/**
* Tests both the unit name iterator and the name-unit entry iterator
*
@@ -587,25 +587,25 @@ class UnitDatabaseTest {
public void testUnitIterator() {
// load units
final UnitDatabase database = new UnitDatabase();
-
+
database.addUnit("J", J);
database.addUnit("K", K);
-
+
database.addPrefix("A", A);
database.addPrefix("B", B);
database.addPrefix("C", C);
-
+
final int NUM_UNITS = database.unitMapPrefixless(true).size();
final int NUM_PREFIXES = database.prefixMap(true).size();
-
+
final Iterator<String> nameIterator = database.unitMap().keySet()
.iterator();
final Iterator<Entry<String, Unit>> 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
@@ -614,31 +614,31 @@ class UnitDatabaseTest {
expectedLength++;
unitsWithThisLengthSoFar = 0;
}
-
+
// test that stuff is valid
final String nextName = nameIterator.next();
final Unit nextUnit = database.getUnit(nextName);
final Entry<String, Unit> nextEntry = entryIterator.next();
-
+
assertEquals(expectedLength, nextName.length());
assertEquals(nextName, nextEntry.getKey());
assertEquals(nextUnit, nextEntry.getValue());
-
+
unitsWithThisLengthSoFar++;
}
-
+
// test toString for consistency
final String entryIteratorString = entryIterator.toString();
for (int i = 0; i < 3; i++) {
assertEquals(entryIteratorString, entryIterator.toString());
}
-
+
final String nameIteratorString = nameIterator.toString();
for (int i = 0; i < 3; i++) {
assertEquals(nameIteratorString, nameIterator.toString());
}
}
-
+
/**
* Determine, given a unit name that could mean multiple things, which
* meaning is chosen.
@@ -654,28 +654,28 @@ class UnitDatabaseTest {
public void testUnitPrefixCombinations() {
// load units
final UnitDatabase database = new UnitDatabase();
-
+
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", UnitPrefix.valueOf(17));
-
+
final Unit expected2 = J.times(17);
final Unit actual2 = database.getUnit("ABCJ");
-
+
assertEquals(expected2, actual2);
}
}
diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java
index d3699ca..c93043b 100644
--- a/src/test/java/sevenUnits/unit/UnitTest.java
+++ b/src/test/java/sevenUnits/unit/UnitTest.java
@@ -42,17 +42,17 @@ import sevenUnits.utils.UncertainDouble;
class UnitTest {
/** A random number generator */
private static final Random rng = ThreadLocalRandom.current();
-
+
@Test
public void testAdditionAndSubtraction() {
final LinearUnit inch = Metric.METRE.times(0.0254)
.withName(NameSymbol.of("inch", "in"));
final LinearUnit foot = Metric.METRE.times(0.3048)
.withName(NameSymbol.of("foot", "ft"));
-
+
assertEquals(inch.plus(foot), Metric.METRE.times(0.3302));
assertEquals(foot.minus(inch), Metric.METRE.times(0.2794));
-
+
// test with LinearUnitValue
final LinearUnitValue value1 = LinearUnitValue.getExact(Metric.METRE, 15);
final LinearUnitValue value2 = LinearUnitValue.getExact(foot, 120);
@@ -60,70 +60,70 @@ class UnitTest {
0.5);
final LinearUnitValue value4 = LinearUnitValue.getExact(Metric.KILOGRAM,
60);
-
+
// make sure addition is done correctly
assertEquals(51.576, value1.plus(value2).getValueExact(), 0.001);
assertEquals(15.5, value1.plus(value3).getValueExact());
assertEquals(52.076, value1.plus(value2).plus(value3).getValueExact(),
0.001);
-
+
// make sure addition uses the correct unit, and is still associative
// (ignoring floating-point rounding errors)
assertEquals(Metric.METRE, value1.plus(value2).getUnit());
assertEquals(Metric.METRE, value1.plus(value2).plus(value3).getUnit());
assertEquals(foot, value2.plus(value1).getUnit());
assertTrue(value1.plus(value2).equals(value2.plus(value1), true));
-
+
// make sure errors happen when they should
assertThrows(IllegalArgumentException.class, () -> value1.plus(value4));
assertThrows(IllegalArgumentException.class, () -> value1.minus(value4));
}
-
+
@Test
public void testConversion() {
final LinearUnit metre = Metric.METRE;
final Unit inch = metre.times(0.0254);
-
+
final UnitValue value = UnitValue.of(inch, 75);
-
+
assertEquals(1.9, inch.convertTo(metre, 75), 0.01);
assertEquals(1.9, value.convertTo(metre).getValue(), 0.01);
-
+
// try random stuff
for (int i = 0; i < 1000; i++) {
// initiate random values
final double conversionFactor = UnitTest.rng.nextDouble() * 1000000;
final double testValue = UnitTest.rng.nextDouble() * 1000000;
final double expected = testValue * conversionFactor;
-
+
// test
final Unit unit = Metric.METRE.times(conversionFactor);
final double actual = unit.convertToBase(testValue);
-
+
assertEquals(actual, expected,
expected * DecimalComparison.DOUBLE_EPSILON);
}
}
-
+
@Test
public void testEquals() {
final LinearUnit metre = Metric.METRE;
final Unit meter = Metric.BaseUnits.METRE.asLinearUnit();
-
+
assertEquals(metre, meter);
}
-
+
@Test
public void testIsMetric() {
final Unit metre = Metric.METRE;
final Unit megasecond = Metric.SECOND.withPrefix(Metric.MEGA);
final Unit hour = Metric.HOUR;
-
+
assertTrue(metre.isMetric());
assertTrue(megasecond.isMetric());
assertFalse(hour.isMetric());
}
-
+
@Test
public void testMultiplicationAndDivision() {
// test unit-times-unit multiplication
@@ -131,29 +131,29 @@ class UnitTest {
.times(Metric.METRE.toExponent(2))
.dividedBy(Metric.SECOND.toExponent(2));
final LinearUnit actualJoule = Metric.JOULE;
-
+
assertEquals(generatedJoule, actualJoule);
-
+
// test multiplication by conversion factors
final LinearUnit kilometre = Metric.METRE.times(1000);
final LinearUnit hour = Metric.SECOND.times(3600);
final LinearUnit generatedKPH = kilometre.dividedBy(hour);
-
+
final LinearUnit actualKPH = Metric.METRE.dividedBy(Metric.SECOND)
.dividedBy(3.6);
-
+
assertEquals(generatedKPH, actualKPH);
}
-
+
@Test
public void testPrefixes() {
final LinearUnit generatedKilometre = Metric.METRE
.withPrefix(Metric.KILO);
final LinearUnit actualKilometre = Metric.METRE.times(1000);
-
+
assertEquals(generatedKilometre, actualKilometre);
}
-
+
/**
* Tests converting an uncertain LinearUnitValue to a string.
*
@@ -163,13 +163,13 @@ class UnitTest {
public void testValueToString1() {
final LinearUnitValue value = LinearUnitValue.of(Metric.METRE,
UncertainDouble.of(10, 0.24));
-
- assertEquals("(10.0 ± 0.2) m", value.toString());
- assertEquals("(10.0 ± 0.2) m",
+
+ assertEquals("(10.0 � 0.2) m", value.toString());
+ assertEquals("(10.0 � 0.2) m",
value.toString(true, RoundingMode.HALF_EVEN));
assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN));
}
-
+
/**
* Tests converting a certain LinearUnitValue to a string.
*
@@ -179,13 +179,13 @@ class UnitTest {
public void testValueToString2() {
final LinearUnitValue value = LinearUnitValue.of(Metric.METRE,
UncertainDouble.of(10, 0));
-
+
assertEquals("10.0 m", value.toString());
- assertEquals("(10.0 ± 0.0) m",
+ assertEquals("(10.0 � 0.0) m",
value.toString(true, RoundingMode.HALF_EVEN));
assertEquals("10.0 m", value.toString(false, RoundingMode.HALF_EVEN));
}
-
+
/**
* Tests converting an unnamed LinearUnitValue to a string.
*
@@ -196,11 +196,11 @@ class UnitTest {
final LinearUnitValue value = LinearUnitValue.of(
Metric.METRE.withName(NameSymbol.EMPTY),
UncertainDouble.of(10, 0.24));
-
+
assertEquals("10.0 unnamed unit (= 10.0 m)",
value.toString(false, RoundingMode.HALF_EVEN));
}
-
+
/**
* Tests converting a named UnitValue to a string.
*
@@ -209,10 +209,10 @@ class UnitTest {
@Test
public void testValueToString4() {
final UnitValue value = UnitValue.of(BritishImperial.FAHRENHEIT, 80);
-
+
assertEquals("80.0 \u00B0F", value.toString());
}
-
+
/**
* Tests converting an unnamed UnitValue to a string.
*
@@ -222,7 +222,7 @@ class UnitTest {
public void testValueToString5() {
final UnitValue value = UnitValue
.of(USCustomary.FAHRENHEIT.withName(NameSymbol.EMPTY), 50);
-
+
assertEquals("50.0 unnamed unit (= 283.15 K)", value.toString());
}
}
diff --git a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java
index 6b5f9cf..868385b 100644
--- a/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java
+++ b/src/test/java/sevenUnits/utils/ConditionalExistenceCollectionsTest.java
@@ -43,7 +43,7 @@ import sevenUnits.utils.ConditionalExistenceCollections.ConditionalExistenceIter
* @since 2019-10-16
*/
class ConditionalExistenceCollectionsTest {
-
+
/**
* The returned iterator ignores elements that don't start with "a".
*
@@ -57,7 +57,7 @@ class ConditionalExistenceCollectionsTest {
.conditionalExistenceIterator(it, s -> s.startsWith("a"));
return cit;
}
-
+
/**
* The returned map ignores mappings where the value is zero.
*
@@ -75,7 +75,7 @@ class ConditionalExistenceCollectionsTest {
e -> !Integer.valueOf(0).equals(e.getValue()));
return conditionalMap;
}
-
+
/**
* Test method for the ConditionalExistenceMap's containsKey method.
*/
@@ -87,7 +87,7 @@ class ConditionalExistenceCollectionsTest {
assertFalse(map.containsKey("five"));
assertFalse(map.containsKey("zero"));
}
-
+
/**
* Test method for the ConditionalExistenceMap's containsValue method.
*/
@@ -99,7 +99,7 @@ class ConditionalExistenceCollectionsTest {
assertFalse(map.containsValue(5));
assertFalse(map.containsValue(0));
}
-
+
/**
* Test method for the ConditionalExistenceMap's entrySet method.
*/
@@ -110,7 +110,7 @@ class ConditionalExistenceCollectionsTest {
assertTrue(e.getValue() != 0);
}
}
-
+
/**
* Test method for the ConditionalExistenceMap's get method.
*/
@@ -122,7 +122,7 @@ class ConditionalExistenceCollectionsTest {
assertEquals(null, map.get("five"));
assertEquals(null, map.get("zero"));
}
-
+
/**
* Test method for the ConditionalExistenceCollection's iterator.
*/
@@ -130,23 +130,23 @@ class ConditionalExistenceCollectionsTest {
void testIterator() {
final ConditionalExistenceIterator<String> testIterator = this
.getTestIterator();
-
+
assertTrue(testIterator.hasNext);
assertTrue(testIterator.hasNext());
assertEquals("aa", testIterator.nextElement);
assertEquals("aa", testIterator.next());
-
+
assertTrue(testIterator.hasNext);
assertTrue(testIterator.hasNext());
assertEquals("ab", testIterator.nextElement);
assertEquals("ab", testIterator.next());
-
+
assertFalse(testIterator.hasNext);
assertFalse(testIterator.hasNext());
assertEquals(null, testIterator.nextElement);
assertThrows(NoSuchElementException.class, testIterator::next);
}
-
+
/**
* Test method for the ConditionalExistenceMap's keySet operation.
*/
@@ -155,7 +155,7 @@ class ConditionalExistenceCollectionsTest {
final Map<String, Integer> map = this.getTestMap();
assertFalse(map.keySet().contains("zero"));
}
-
+
/**
* Test method for the ConditionalExistenceMap's values operation.
*/
@@ -164,5 +164,5 @@ class ConditionalExistenceCollectionsTest {
final Map<String, Integer> map = this.getTestMap();
assertFalse(map.values().contains(0));
}
-
+
}
diff --git a/src/test/java/sevenUnits/utils/ExpressionParserTest.java b/src/test/java/sevenUnits/utils/ExpressionParserTest.java
index a954b12..3a95285 100644
--- a/src/test/java/sevenUnits/utils/ExpressionParserTest.java
+++ b/src/test/java/sevenUnits/utils/ExpressionParserTest.java
@@ -38,12 +38,11 @@ import org.junit.jupiter.params.provider.MethodSource;
class ExpressionParserTest {
private static final ExpressionParser<Integer> 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();
-
+ .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();
+
/**
* The expressions used in the expression parsing tests
*/
@@ -51,15 +50,15 @@ class ExpressionParserTest {
// test parsing of expressions
"1 + 2 ^ 5 * 3", "(1 + 2) ^ 5 * 3",
"12 * 5 + (3 ^ (2 * 3) - 72) / (3 + 3 * 2)",
-
+
// ensure it normally goes from left to right
"1 + 2 + 3 + 4", "12 - 4 - 3", "12 - (4 - 3)", "1 / 2 + 3");
-
+
/**
* The expected results for evaluating these expressions
*/
private static final int[] RESULTS = { 97, 729, 133, 10, 5, 11, 3 };
-
+
/**
* @return A stream of objects, where each one is an expression and the
* expected result
@@ -69,7 +68,7 @@ class ExpressionParserTest {
return IntStream.range(0, TEST_EXPRESSIONS.size())
.mapToObj(i -> Arguments.of(TEST_EXPRESSIONS.get(i), RESULTS[i]));
}
-
+
/**
* Test method for
* {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}.
diff --git a/src/test/java/sevenUnits/utils/ObjectProductTest.java b/src/test/java/sevenUnits/utils/ObjectProductTest.java
index 15ff277..8c6b353 100644
--- a/src/test/java/sevenUnits/utils/ObjectProductTest.java
+++ b/src/test/java/sevenUnits/utils/ObjectProductTest.java
@@ -32,7 +32,8 @@ import org.junit.jupiter.api.Test;
import sevenUnits.unit.Metric;
/**
- * Tests for {@link ObjectProduct} using BaseDimension as a test object. This is NOT part of this program's public API.
+ * Tests for {@link ObjectProduct} using BaseDimension as a test object. This is
+ * NOT part of this program's public API.
*
* @author Adrien Hopkins
* @since 2018-12-12
diff --git a/src/test/java/sevenUnits/utils/SemanticVersionTest.java b/src/test/java/sevenUnits/utils/SemanticVersionTest.java
index 877b258..1e59ae3 100644
--- a/src/test/java/sevenUnits/utils/SemanticVersionTest.java
+++ b/src/test/java/sevenUnits/utils/SemanticVersionTest.java
@@ -48,20 +48,20 @@ public final class SemanticVersionTest {
"1.0.0 not compatible with 1.0.5");
assertTrue(stableVersion(1, 3, 1).compatibleWith(stableVersion(1, 4, 0)),
"1.3.1 not compatible with 1.4.0");
-
+
// 0.y.z should not be compatible with any other version
assertFalse(stableVersion(0, 4, 0).compatibleWith(stableVersion(0, 4, 1)),
"0.4.0 compatible with 0.4.1 (0.y.z versions should be treated as unstable/incompatbile)");
-
+
// upgrading major version should = incompatible
assertFalse(stableVersion(1, 0, 0).compatibleWith(stableVersion(2, 0, 0)),
"1.0.0 compatible with 2.0.0");
-
+
// dowgrade should = incompatible
assertFalse(stableVersion(1, 1, 0).compatibleWith(stableVersion(1, 0, 0)),
"1.1.0 compatible with 1.0.0");
}
-
+
/**
* Tests {@link SemanticVersionNumber#toString} for complex version numbers
*
@@ -79,7 +79,7 @@ public final class SemanticVersionTest {
.preRelease("x-y-z", "--").build();
assertEquals("1.0.0-x-y-z.--", v3.toString());
}
-
+
/**
* Tests that complex version can be created and their parts read
*
@@ -94,7 +94,7 @@ public final class SemanticVersionTest {
assertEquals(3, v1.patchVersion());
assertEquals(List.of("1", "2", "3"), v1.preReleaseIdentifiers());
assertEquals(List.of(), v1.buildMetadata());
-
+
final SemanticVersionNumber v2 = builder(4, 5, 6).preRelease("abc", 123)
.buildMetadata("2022-02-19").build();
assertEquals(4, v2.majorVersion());
@@ -102,7 +102,7 @@ public final class SemanticVersionTest {
assertEquals(6, v2.patchVersion());
assertEquals(List.of("abc", "123"), v2.preReleaseIdentifiers());
assertEquals(List.of("2022-02-19"), v2.buildMetadata());
-
+
final SemanticVersionNumber v3 = builder(1, 0, 0)
.preRelease("x-y-z", "--").build();
assertEquals(1, v3.majorVersion());
@@ -111,7 +111,7 @@ public final class SemanticVersionTest {
assertEquals(List.of("x-y-z", "--"), v3.preReleaseIdentifiers());
assertEquals(List.of(), v3.buildMetadata());
}
-
+
/**
* Test that semantic version strings can be parsed correctly
*
@@ -132,7 +132,7 @@ public final class SemanticVersionTest {
"1.0.0+abc is treated as invalid");
assertTrue(isValidVersionString("1.0.0-abc+def"),
"1.0.0-abc+def is treated as invalid");
-
+
// test that invalid versions don't match
assertFalse(isValidVersionString("1.0"),
"1.0 is treated as valid (patch should be required)");
@@ -142,7 +142,7 @@ public final class SemanticVersionTest {
"1.0.0- is treated as valid (pre-release must not be empty)");
assertFalse(isValidVersionString("1.0.0+"),
"1.0.0+ is treated as valid (build metadata must not be empty)");
-
+
// test that versions can be parsed
assertEquals(stableVersion(1, 0, 0), fromString("1.0.0"),
"Could not parse 1.0.0");
@@ -152,7 +152,7 @@ public final class SemanticVersionTest {
fromString("1.2.3-abc.56.def+2022abc99"),
"Could not parse 1.2.3-abc.56.def+2022abc99");
}
-
+
/**
* Ensures it is impossible to create invalid version numbers
*/
@@ -168,7 +168,7 @@ public final class SemanticVersionTest {
assertThrows(IllegalArgumentException.class,
() -> stableVersion(-3, 0, 7),
"Negative major version number tolerated by stableVersion");
-
+
// preRelease()
assertThrows(IllegalArgumentException.class,
() -> preRelease(1, 0, -1, "test", 2),
@@ -190,7 +190,7 @@ public final class SemanticVersionTest {
assertThrows(IllegalArgumentException.class,
() -> preRelease(1, 0, 0, "abc+cde", 1),
"Invalid string tolerated by preRelease");
-
+
// builder()
assertThrows(IllegalArgumentException.class, () -> builder(1, 0, -1),
"Negative patch tolerated by builder");
@@ -198,7 +198,7 @@ public final class SemanticVersionTest {
"Negative minor version number tolerated by builder");
assertThrows(IllegalArgumentException.class, () -> builder(-3, 0, 7),
"Negative major version number tolerated by builder");
-
+
final SemanticVersionNumber.Builder testBuilder = builder(1, 2, 3);
// note: builder.buildMetadata(null) doesn't even compile lol
// builder.buildMetadata
@@ -220,7 +220,7 @@ public final class SemanticVersionTest {
assertThrows(IllegalArgumentException.class,
() -> testBuilder.buildMetadata(List.of("")),
"Invalid string tolerated by builder.buildMetadata(List<String>)");
-
+
// builder.preRelease
assertThrows(NullPointerException.class,
() -> testBuilder.preRelease(null, "abc"),
@@ -240,7 +240,7 @@ public final class SemanticVersionTest {
assertThrows(IllegalArgumentException.class,
() -> testBuilder.preRelease(List.of("")),
"Invalid string tolerated by builder.preRelease(List<String>)");
-
+
// the overloadings that accept numeric arguments
assertThrows(IllegalArgumentException.class,
() -> testBuilder.preRelease(-1),
@@ -257,12 +257,12 @@ public final class SemanticVersionTest {
assertThrows(IllegalArgumentException.class,
() -> testBuilder.preRelease("#$#c", 1),
"Invalid string tolerated by builder.preRelease(String, int)");
-
+
// ensure all these attempts didn't change the builder
assertEquals(builder(1, 2, 3), testBuilder,
"Attempts at making invalid version number succeeded despite throwing errors");
}
-
+
/**
* Test for {@link SemanticVersionNumber#isStable}
*
@@ -282,7 +282,7 @@ public final class SemanticVersionTest {
.isStable(),
"9.9.99+lots-of-metadata.abc123.2022 should be stable but is not");
}
-
+
/**
* Tests that the versions are ordered by
* {@link SemanticVersionNumber#compareTo} according to official rules. Tests
@@ -311,7 +311,7 @@ public final class SemanticVersionTest {
final SemanticVersionNumber v210 = stableVersion(2, 1, 0);
final SemanticVersionNumber v211 = stableVersion(2, 1, 1);
final SemanticVersionNumber v300 = stableVersion(3, 0, 0);
-
+
// test order of version numbers
assertTrue(v100a.compareTo(v100a1) < 0, "1.0.0-alpha >= 1.0.0-alpha.1");
assertTrue(v100a1.compareTo(v100ab) < 0,
@@ -327,25 +327,25 @@ public final class SemanticVersionTest {
assertTrue(v201.compareTo(v210) < 0, "2.0.1 >= 2.1.0");
assertTrue(v210.compareTo(v211) < 0, "2.1.0 >= 2.1.1");
assertTrue(v211.compareTo(v300) < 0, "2.1.1 >= 3.0.0");
-
+
// test symmetry - assume previous tests passed
assertTrue(v100a1.compareTo(v100a) > 0, "1.0.0-alpha.1 <= 1.0.0-alpha");
assertTrue(v100.compareTo(v100rc1) > 0, "1.0.0 <= 1.0.0-rc.1");
assertTrue(v300.compareTo(v211) > 0, "3.0.0 <= 2.1.1");
-
+
// test transitivity
assertTrue(v100a.compareTo(v100b11) < 0, "1.0.0-alpha >= 1.0.0-beta.11");
assertTrue(v100b.compareTo(v200) < 0, "1.0.0-beta >= 2.0.0");
assertTrue(v100.compareTo(v300) < 0, "1.0.0 >= 3.0.0");
assertTrue(v100a.compareTo(v300) < 0, "1.0.0-alpha >= 3.0.0");
-
+
// test metadata is ignored
assertEquals(0, v100.compareTo(v100plus), "Build metadata not ignored");
// test metadata is NOT ignored by alternative comparator
assertTrue(BUILD_METADATA_COMPARATOR.compare(v100, v100plus) > 0,
"Build metadata ignored by BUILD_METADATA_COMPARATOR");
}
-
+
/**
* Tests that simple stable versions can be created and their parts read
*
@@ -357,13 +357,13 @@ public final class SemanticVersionTest {
assertEquals(1, v100.majorVersion());
assertEquals(0, v100.minorVersion());
assertEquals(0, v100.patchVersion());
-
+
final SemanticVersionNumber v925 = stableVersion(9, 2, 5);
assertEquals(9, v925.majorVersion());
assertEquals(2, v925.minorVersion());
assertEquals(5, v925.patchVersion());
}
-
+
/**
* Tests that {@link SemanticVersionNumber#toString} works for simple version
* numbers
@@ -374,11 +374,11 @@ public final class SemanticVersionTest {
public void testSimpleToString() {
final SemanticVersionNumber v100 = stableVersion(1, 0, 0);
assertEquals("1.0.0", v100.toString());
-
+
final SemanticVersionNumber v845a1 = preRelease(8, 4, 5, "alpha", 1);
assertEquals("8.4.5-alpha.1", v845a1.toString());
}
-
+
/**
* Tests that simple unstable versions can be created and their parts read
*
diff --git a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
index 5ccef28..36b373b 100644
--- a/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
+++ b/src/test/java/sevenUnits/utils/UncertainDoubleTest.java
@@ -43,36 +43,36 @@ class UncertainDoubleTest {
assertTrue(of(2.0, 0.5).compareTo(of(1.0, 0.1)) > 0);
assertTrue(of(2.0, 0.5).compareTo(of(3.0, 0.1)) < 0);
}
-
+
/**
* Tests the ___exact operations
*/
@Test
final void testExactOperations() {
final UncertainDouble x = UncertainDouble.of(Math.PI, 0.1);
-
+
// slightly different because roundoff errors
final UncertainDouble x1 = UncertainDouble.of(Math.PI + Math.E - Math.E,
0.1);
final UncertainDouble x2 = UncertainDouble.of(Math.PI * Math.E / Math.E,
0.1);
-
+
// get results
final UncertainDouble result1 = x.plusExact(Math.E).minusExact(Math.E);
final UncertainDouble result2 = x.timesExact(Math.E)
.dividedByExact(Math.E);
-
+
// test that these operations work & don't change uncertainty
assertEquals(x1, result1);
assertTrue(x.equivalent(result1));
assertEquals(x2, result2);
assertTrue(x.equivalent(result2));
-
+
// exponents are different
assertEquals(Math.pow(Math.PI, Math.E),
x.toExponentExact(Math.E).value());
}
-
+
/**
* Test for {@link UncertainDouble#fromRoundedString}
*
@@ -82,29 +82,29 @@ class UncertainDoubleTest {
final void testFromRoundedString() {
assertEquals(of(12345.678, 0.001), fromRoundedString("12345.678"));
}
-
+
/**
* Test for {@link UncertainDouble#fromString}
*/
@Test
final void testFromString() {
// valid strings
- assertEquals(of(2.0, 0.5), fromString("2.0 ± 0.5"));
+ assertEquals(of(2.0, 0.5), fromString("2.0 � 0.5"));
assertEquals(of(2.0, 0.5), fromString("2.0 +- 0.5"));
assertEquals(of(2.0, 0.0), fromString("2.0"));
-
+
// invalid strings
- for (final String s : List.of("2.A", "A", "2.0 ± .", "± 3.5")) {
+ for (final String s : List.of("2.A", "A", "2.0 � .", "� 3.5")) {
assertThrows(IllegalArgumentException.class, () -> fromString(s));
}
-
+
// back and forth
- assertEquals("2.0 ± 0.5", of(2.0, 0.5).toString());
+ assertEquals("2.0 � 0.5", of(2.0, 0.5).toString());
assertEquals("2.0", of(2.0, 0).toString());
}
-
+
@Test
final void testHashCode() {
- assertEquals(of(2.0, 0.5).hashCode(), fromString("2.0 ± 0.5").hashCode());
+ assertEquals(of(2.0, 0.5).hashCode(), fromString("2.0 � 0.5").hashCode());
}
}
diff --git a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java
index 8ea3fd0..476e407 100644
--- a/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java
+++ b/src/test/java/sevenUnitsGUI/PrefixRepetitionTest.java
@@ -54,7 +54,7 @@ class PrefixRepetitionTest {
assertFalse(COMPLEX_REPETITION.test(List.of(Metric.YOTTA, Metric.MILLI)),
"Complex repetition does not factor direction of prefixes");
}
-
+
/**
* Tests the {@code NO_REPETITION} rule.
*
@@ -68,7 +68,7 @@ class PrefixRepetitionTest {
assertTrue(NO_REPETITION.test(List.of(Metric.MILLI)));
assertTrue(NO_REPETITION.test(List.of()), "Empty list yields false");
}
-
+
/**
* Tests the {@code NO_RESTRICTION} rule.
*
@@ -82,7 +82,7 @@ class PrefixRepetitionTest {
assertTrue(NO_RESTRICTION.test(List.of(Metric.MILLI)));
assertTrue(NO_RESTRICTION.test(List.of()));
}
-
+
/**
* Ensures that the complex repetition rule allows valid prefix lists.
*
@@ -95,7 +95,7 @@ class PrefixRepetitionTest {
assertTrue(COMPLEX_REPETITION.test(List.of(Metric.KILO)),
"Single prefix not allowed");
assertTrue(COMPLEX_REPETITION.test(List.of()), "No prefixes not allowed");
-
+
// valid complex repetition
assertTrue(COMPLEX_REPETITION.test(List.of(Metric.YOTTA, Metric.KILO)),
"Valid complex repetition (kiloyotta) not allowed");
@@ -109,7 +109,7 @@ class PrefixRepetitionTest {
COMPLEX_REPETITION.test(List.of(Metric.YOTTA, Metric.YOTTA,
Metric.KILO, Metric.DEKA)),
"Valid complex repetition (dekakiloyottayotta) not allowed");
-
+
// valid with negative prefixes
assertTrue(COMPLEX_REPETITION.test(List.of(Metric.YOCTO, Metric.MILLI)),
"Valid complex repetition (milliyocto) not allowed");
diff --git a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java
index ca238fe..305d0d7 100644
--- a/src/test/java/sevenUnitsGUI/PrefixSearchTest.java
+++ b/src/test/java/sevenUnitsGUI/PrefixSearchTest.java
@@ -45,7 +45,7 @@ class PrefixSearchTest {
private static final PrefixSearchRule getCommonRuleCopy() {
return getCoherentOnlyRule(Set.of(Metric.KILO, Metric.MILLI));
}
-
+
/**
* Test method for
* {@link sevenUnitsGUI.PrefixSearchRule#apply(java.util.Map.Entry)}, for a
@@ -60,11 +60,11 @@ class PrefixSearchTest {
Metric.KILOMETRE, "millimetre", Metric.MILLIMETRE);
final var actual = COMMON_PREFIXES
.apply(Map.entry("metre", Metric.METRE));
-
+
assertEquals(expected, actual,
"Prefixes not correctly applied to coherent unit.");
}
-
+
/**
* Test method for
* {@link sevenUnitsGUI.PrefixSearchRule#equals(java.lang.Object)}.
@@ -76,11 +76,11 @@ class PrefixSearchTest {
final void testEquals() {
assertEquals(getCommonRuleCopy(), getCommonRuleCopy(),
"equals considers something other than prefixes/rule");
-
+
assertNotEquals(getCoherentOnlyRule(Set.of()), getUniversalRule(Set.of()),
"equals ignores rule");
}
-
+
/**
* Test method for {@link sevenUnitsGUI.PrefixSearchRule#getPrefixes()}.
*
@@ -93,7 +93,7 @@ class PrefixSearchTest {
assertEquals(Metric.ALL_PREFIXES,
PrefixSearchRule.ALL_METRIC_PREFIXES.getPrefixes());
}
-
+
/**
* Test method for {@link sevenUnitsGUI.PrefixSearchRule#hashCode()}.
*
@@ -105,7 +105,7 @@ class PrefixSearchTest {
assertEquals(getCommonRuleCopy().hashCode(),
getCommonRuleCopy().hashCode());
}
-
+
/**
* Tests prefix searching for a non-coherent unit and
* {@link PrefixSearchRule#COMMON_PREFIXES}.
@@ -118,10 +118,10 @@ class PrefixSearchTest {
final var input = Map.entry("inch", BritishImperial.Length.INCH);
final var expected = Map.ofEntries(input);
final var actual = COMMON_PREFIXES.apply(input);
-
+
assertEquals(expected, actual, "Prefixes applied to non-coherent unit.");
}
-
+
/**
* Tests that {@link PrefixSearchRule#NO_PREFIXES} returns the original unit.
*
@@ -141,7 +141,7 @@ class PrefixSearchTest {
}
}
}
-
+
/**
* Test method for {@link sevenUnitsGUI.PrefixSearchRule#toString()}.
*
@@ -154,5 +154,5 @@ class PrefixSearchTest {
"Apply the following prefixes: [kilo (\u00D7 1000.0), milli (\u00D7 0.001)]",
COMMON_PREFIXES.toString());
}
-
+
}
diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java
index 13d7986..9e25a08 100644
--- a/src/test/java/sevenUnitsGUI/PresenterTest.java
+++ b/src/test/java/sevenUnitsGUI/PresenterTest.java
@@ -18,6 +18,7 @@ package sevenUnitsGUI;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.math.RoundingMode;
import java.nio.file.Path;
@@ -63,14 +64,14 @@ public final class PresenterTest {
"test-settings.txt");
static final Set<Unit> testUnits = Set.of(Metric.METRE, Metric.KILOMETRE,
Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR);
-
+
static final Set<ObjectProduct<BaseDimension>> testDimensions = Set
.of(Metric.Dimensions.LENGTH, Metric.Dimensions.VELOCITY);
-
+
private static final Stream<Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>>> SEARCH_RULES = Stream
.of(PrefixSearchRule.NO_PREFIXES, PrefixSearchRule.COMMON_PREFIXES,
PrefixSearchRule.ALL_METRIC_PREFIXES);
-
+
/**
* @return rounding rules used by {@link #testRoundingRules}
* @since v0.4.0
@@ -80,18 +81,18 @@ public final class PresenterTest {
final var SCIENTIFIC_ROUNDING = StandardDisplayRules.uncertaintyBased();
final var INTEGER_ROUNDING = StandardDisplayRules.fixedDecimals(0);
final var SIG_FIG_ROUNDING = StandardDisplayRules.fixedPrecision(4);
-
+
return Stream.of(SCIENTIFIC_ROUNDING, INTEGER_ROUNDING, SIG_FIG_ROUNDING);
}
-
+
private static final Stream<Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>>> getSearchRules() {
return SEARCH_RULES;
}
-
+
private static final Set<String> names(Set<? extends Nameable> units) {
return units.stream().map(Nameable::getName).collect(Collectors.toSet());
}
-
+
/**
* Test method for {@link Presenter#convertExpressions}
*
@@ -103,20 +104,20 @@ public final class PresenterTest {
// setup
final ViewBot viewBot = new ViewBot();
final Presenter presenter = new Presenter(viewBot);
-
+
viewBot.setFromExpression("10000.0 m");
viewBot.setToExpression("km");
-
+
// convert expression
presenter.convertExpressions();
-
+
// test result
final List<UnitConversionRecord> outputs = viewBot
.expressionConversionList();
assertEquals("10000.0 m = 10.00000 km",
outputs.get(outputs.size() - 1).toString());
}
-
+
/**
* Tests that unit-conversion Views can correctly convert units
*
@@ -128,16 +129,16 @@ public final class PresenterTest {
// setup
final ViewBot viewBot = new ViewBot();
final Presenter presenter = new Presenter(viewBot);
-
+
viewBot.setFromUnitNames(names(testUnits));
viewBot.setToUnitNames(names(testUnits));
viewBot.setFromSelection("metre");
viewBot.setToSelection("kilometre");
viewBot.setInputValue("10000.0");
-
+
// convert units
presenter.convertUnits();
-
+
/*
* use result from system as expected - I'm not testing unit conversion
* here (that's for the backend tests), I'm just testing that it correctly
@@ -153,7 +154,7 @@ public final class PresenterTest {
expectedOutput.getValue().toString(false, RoundingMode.HALF_EVEN));
assertEquals(List.of(expectedUC), viewBot.unitConversionList());
}
-
+
/**
* Tests that duplicate units are successfully removed, if that is asked for
*
@@ -164,7 +165,7 @@ public final class PresenterTest {
void testDuplicateUnits() {
final var metre = Metric.METRE;
final var meter = Metric.METRE.withName(NameSymbol.of("meter", "m"));
-
+
// load 2 duplicate units
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
@@ -173,20 +174,20 @@ public final class PresenterTest {
presenter.database.addUnit("meter", meter);
presenter.setOneWayConversionEnabled(false);
presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES);
-
+
// test that only one of them is included if duplicate units disabled
presenter.setShowDuplicates(false);
presenter.updateView();
assertEquals(1, viewBot.getFromUnitNames().size());
assertEquals(1, viewBot.getToUnitNames().size());
-
+
// test that both of them is included if duplicate units enabled
presenter.setShowDuplicates(true);
presenter.updateView();
assertEquals(2, viewBot.getFromUnitNames().size());
assertEquals(2, viewBot.getToUnitNames().size());
}
-
+
/**
* Tests that one-way conversion correctly filters From and To units
*
@@ -199,7 +200,7 @@ public final class PresenterTest {
final var allNames = Set.of("metre", "inch", "tempC");
final var metricNames = Set.of("metre", "tempC");
final var nonMetricNames = Set.of("inch", "tempC");
-
+
// load view with one metric and one non-metric unit
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
@@ -208,19 +209,19 @@ public final class PresenterTest {
presenter.database.addUnit("inch", BritishImperial.Length.INCH);
presenter.database.addUnit("tempC", Metric.CELSIUS);
presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES);
-
+
// test that units are removed from each side when one-way conversion is
// enabled
presenter.setOneWayConversionEnabled(true);
assertEquals(nonMetricNames, viewBot.getFromUnitNames());
assertEquals(metricNames, viewBot.getToUnitNames());
-
+
// test that units are kept when one-way conversion is disabled
presenter.setOneWayConversionEnabled(false);
assertEquals(allNames, viewBot.getFromUnitNames());
assertEquals(allNames, viewBot.getToUnitNames());
}
-
+
/**
* Tests the prefix-viewing functionality.
*
@@ -234,23 +235,23 @@ public final class PresenterTest {
final var presenter = new Presenter(viewBot);
viewBot.setViewablePrefixNames(Set.of("kilo", "milli"));
presenter.setNumberDisplayRule(UncertainDouble::toString);
-
+
// view prefix
viewBot.setViewedPrefixName("kilo");
presenter.prefixSelected(); // just in case
-
+
// get correct values
final var expectedNameSymbol = presenter.database.getPrefix("kilo")
.getNameSymbol();
final var expectedMultiplierString = String
.valueOf(Metric.KILO.getMultiplier());
-
+
// test that presenter's values are correct
final var prefixRecord = viewBot.prefixViewList().get(0);
assertEquals(expectedNameSymbol, prefixRecord.getNameSymbol());
assertEquals(expectedMultiplierString, prefixRecord.multiplierString());
}
-
+
/**
* Tests that rounding rules are used correctly.
*
@@ -264,13 +265,13 @@ public final class PresenterTest {
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
presenter.setNumberDisplayRule(roundingRule);
-
+
// convert and round
viewBot.setInputValue("12345.6789");
viewBot.setFromSelection("metre");
viewBot.setToSelection("kilometre");
presenter.convertUnits();
-
+
// test the result of the rounding
final String expectedOutputString = roundingRule
.apply(UncertainDouble.fromRoundedString("12.3456789"));
@@ -278,7 +279,7 @@ public final class PresenterTest {
.outputValueString();
assertEquals(expectedOutputString, actualOutputString);
}
-
+
/**
* Tests that the Presenter correctly applies search rules.
*
@@ -293,15 +294,15 @@ public final class PresenterTest {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
-
+
presenter.setSearchRule(searchRule);
presenter.setOneWayConversionEnabled(false);
-
+
presenter.database.clear();
presenter.database.addUnit("metre", Metric.METRE);
presenter.database.addUnit("inch", BritishImperial.Length.INCH);
presenter.updateView();
-
+
// create expected output based on rule
final Set<String> expectedOutput = new HashSet<>();
expectedOutput.addAll(searchRule
@@ -309,11 +310,11 @@ public final class PresenterTest {
expectedOutput.addAll(
searchRule.apply(Map.entry("metre", Metric.METRE)).keySet());
final Set<String> actualOutput = viewBot.getFromUnitNames();
-
+
// test output
assertEquals(expectedOutput, actualOutput);
}
-
+
/**
* Tests that settings can be saved to and loaded from a file.
*
@@ -325,20 +326,21 @@ public final class PresenterTest {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
-
+
// set and save custom settings
presenter.setOneWayConversionEnabled(true);
presenter.setShowDuplicates(true);
presenter.setNumberDisplayRule(StandardDisplayRules.fixedPrecision(11));
presenter.setPrefixRepetitionRule(
DefaultPrefixRepetitionRule.COMPLEX_REPETITION);
- presenter.saveSettings(TEST_SETTINGS);
-
+ assumeTrue(presenter.writeSettings(TEST_SETTINGS),
+ "Could not write to settings file.");
+
// overwrite custom settings
presenter.setOneWayConversionEnabled(false);
presenter.setShowDuplicates(false);
presenter.setNumberDisplayRule(StandardDisplayRules.uncertaintyBased());
-
+
// load settings & test that they're the same
presenter.loadSettings(TEST_SETTINGS);
assertTrue(presenter.oneWayConversionEnabled());
@@ -346,7 +348,7 @@ public final class PresenterTest {
assertEquals(StandardDisplayRules.fixedPrecision(11),
presenter.getNumberDisplayRule());
}
-
+
/**
* Ensures the Presenter generates the correct data upon a unit-viewing.
*
@@ -359,12 +361,12 @@ public final class PresenterTest {
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
viewBot.setViewableUnitNames(names(testUnits));
-
+
// view unit
viewBot.setViewedUnitName("metre");
presenter.unitNameSelected(); // just in case this isn't triggered
// automatically
-
+
// get correct values
final var expectedNameSymbol = presenter.database.getUnit("metre")
.getNameSymbol();
@@ -372,7 +374,7 @@ public final class PresenterTest {
final var expectedDimensionName = presenter
.getDimensionName(Metric.METRE.getDimension());
final var expectedUnitType = UnitType.METRIC;
-
+
// test for correctness
final var viewRecord = viewBot.unitViewList().get(0);
assertEquals(expectedNameSymbol, viewRecord.getNameSymbol());
@@ -380,7 +382,7 @@ public final class PresenterTest {
assertEquals(expectedDimensionName, viewRecord.dimensionName());
assertEquals(expectedUnitType, viewRecord.unitType());
}
-
+
/**
* Test for {@link Presenter#updateView()}
*
@@ -394,7 +396,7 @@ public final class PresenterTest {
final Presenter presenter = new Presenter(viewBot);
presenter.setOneWayConversionEnabled(false);
presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES);
-
+
// override default database units
presenter.database.clear();
for (final Unit unit : testUnits) {
@@ -404,18 +406,18 @@ public final class PresenterTest {
presenter.database.addDimension(
dimension.getPrimaryName().orElseThrow(), dimension);
}
-
+
// set from and to units
viewBot.setFromUnitNames(names(testUnits));
viewBot.setToUnitNames(names(testUnits));
viewBot.setDimensionNames(names(testDimensions));
viewBot.setSelectedDimensionName(Metric.Dimensions.LENGTH.getName());
-
+
// filter to length units only, then get the filtered sets of units
presenter.updateView();
final Set<String> fromUnits = viewBot.getFromUnitNames();
final Set<String> toUnits = viewBot.getToUnitNames();
-
+
// test that fromUnits/toUnits is [METRE, KILOMETRE]
assertEquals(Set.of("metre", "kilometre"), fromUnits);
assertEquals(Set.of("metre", "kilometre"), toUnits);
diff --git a/src/test/java/sevenUnitsGUI/RoundingTest.java b/src/test/java/sevenUnitsGUI/RoundingTest.java
index ca1a272..f749f85 100644
--- a/src/test/java/sevenUnitsGUI/RoundingTest.java
+++ b/src/test/java/sevenUnitsGUI/RoundingTest.java
@@ -49,13 +49,13 @@ class RoundingTest {
private static final FixedDecimals ZERO_DECIMALS = fixedDecimals(0);
private static final FixedDecimals TWO_DECIMALS = fixedDecimals(2);
private static final FixedDecimals SIX_DECIMALS = fixedDecimals(6);
-
+
private static final FixedPrecision ONE_SIG_FIG = fixedPrecision(1);
private static final FixedPrecision THREE_SIG_FIGS = fixedPrecision(3);
private static final FixedPrecision TWELVE_SIG_FIGS = fixedPrecision(12);
-
+
private static final UncertaintyBased UNCERTAINTY_BASED = uncertaintyBased();
-
+
// numbers to test rounding with
private static final UncertainDouble INPUT1 = UncertainDouble.of(12.3456789,
0.0);
@@ -65,7 +65,7 @@ class RoundingTest {
0.0);
private static final UncertainDouble INPUT4 = UncertainDouble.of(0.00001234,
0.000001);
-
+
/**
* @return arguments for
* {@link #testFixedDecimalRounding(UncertainDouble, String, String, String)}
@@ -79,7 +79,7 @@ class RoundingTest {
Arguments.of(INPUT3, "12345432", "12345432.10", "12345432.100000"),
Arguments.of(INPUT4, "0", "0.00", "0.000012"));
}
-
+
/**
* @return arguments for
* {@link #testFixedPrecisionRounding(UncertainDouble, String, String, String)}
@@ -93,7 +93,7 @@ class RoundingTest {
Arguments.of(INPUT3, "1E+7", "1.23E+7", "12345432.1000"),
Arguments.of(INPUT4, "0.00001", "0.0000123", "0.0000123400000000"));
}
-
+
/**
* @return arguments for
* {@link #testUncertaintyRounding(UncertainDouble, String)}
@@ -107,7 +107,7 @@ class RoundingTest {
Arguments.of(INPUT3, "1.23454321E7"),
Arguments.of(INPUT4, "0.0000123"));
}
-
+
/**
* Test for {@link FixedDecimals#decimalPlaces()} and
* {@link FixedPrecision#significantFigures()}.
@@ -124,7 +124,7 @@ class RoundingTest {
"TWO_DECIMALS has " + TWO_DECIMALS.decimalPlaces() + " decimals");
assertEquals(6, SIX_DECIMALS.decimalPlaces(),
"SIX_DECIMALS has " + SIX_DECIMALS.decimalPlaces() + " decimals");
-
+
// ensure # of sig figs can be accessed
assertEquals(1, ONE_SIG_FIG.significantFigures(), "ONE_SIG_FIG has "
+ ONE_SIG_FIG.significantFigures() + " significant figures");
@@ -134,7 +134,7 @@ class RoundingTest {
"TWELVE_SIG_FIGS has " + TWELVE_SIG_FIGS.significantFigures()
+ " significant figures");
}
-
+
/**
* Tests that the rounding methods' equals() methods work.
*
@@ -150,14 +150,14 @@ class RoundingTest {
"TWO_DECIMALS == SIX_DECIMALS");
assertTrue(Objects.equals(fixedDecimals(0), fixedDecimals(0)),
"FixedDecimals.equals() depends on something other than decimal places.");
-
+
assertTrue(ONE_SIG_FIG.equals(ONE_SIG_FIG),
"ONE_SIG_FIG does not equal itself");
assertFalse(THREE_SIG_FIGS.equals(TWELVE_SIG_FIGS),
"THREE_SIG_FIGS == TWELVE_SIG_FIGS");
assertTrue(Objects.equals(fixedPrecision(1), fixedPrecision(1)),
"FixedPrecision.equals() depends on something other than significant figures.");
-
+
// test that FixedDecimals is never equal to FixedPrecision
// this unlikely argument is the test - the equals should return false!
@SuppressWarnings("unlikely-arg-type")
@@ -165,7 +165,7 @@ class RoundingTest {
fixedPrecision(4));
assertFalse(differentRulesEqual, "fixedDecimals(4) == fixedPrecision(4)");
}
-
+
/**
* Ensures that fixed decimal rounding works as expected
*
@@ -192,7 +192,7 @@ class RoundingTest {
"TWO_DECIMALS rounded " + input + " as " + SIX_DECIMALS.apply(input)
+ " (should be " + sixDecimalString + ")");
}
-
+
/**
* Ensures that fixed precision rounding works as expected
*
@@ -221,7 +221,7 @@ class RoundingTest {
+ TWELVE_SIG_FIGS.apply(input) + " (should be "
+ twelveSigFigString + ")");
}
-
+
/**
* Tests that {@link StandardDisplayRules#getStandardRule} gets rounding
* rules as intended.
@@ -236,11 +236,11 @@ class RoundingTest {
getStandardRule("Round to 3 significant figures"));
assertEquals(UNCERTAINTY_BASED,
getStandardRule("Uncertainty-Based Rounding"));
-
+
assertThrows(IllegalArgumentException.class,
() -> getStandardRule("Not a rounding rule"));
}
-
+
/**
* Tests that the rounding methods' equals() methods work.
*
@@ -253,7 +253,7 @@ class RoundingTest {
assertEquals(ONE_SIG_FIG.hashCode(), ONE_SIG_FIG.hashCode());
assertEquals(UNCERTAINTY_BASED.hashCode(), UNCERTAINTY_BASED.hashCode());
}
-
+
/**
* Tests that the {@code toString()} methods of the three rounding rule
* classes work correctly.
@@ -267,7 +267,7 @@ class RoundingTest {
assertEquals("Round to 3 significant figures", THREE_SIG_FIGS.toString());
assertEquals("Uncertainty-Based Rounding", UNCERTAINTY_BASED.toString());
}
-
+
/**
* Tests that Uncertainty Rounding works as expected
*
diff --git a/src/test/java/sevenUnitsGUI/TabbedViewTest.java b/src/test/java/sevenUnitsGUI/TabbedViewTest.java
index 00092a4..165718f 100644
--- a/src/test/java/sevenUnitsGUI/TabbedViewTest.java
+++ b/src/test/java/sevenUnitsGUI/TabbedViewTest.java
@@ -36,17 +36,17 @@ class TabbedViewTest {
private static final TabbedView setupView() {
final var view = new TabbedView();
final var presenter = view.getPresenter();
-
+
presenter.setNumberDisplayRule(StandardDisplayRules.uncertaintyBased());
presenter.setPrefixRepetitionRule(
DefaultPrefixRepetitionRule.NO_RESTRICTION);
presenter.setSearchRule(PrefixSearchRule.COMMON_PREFIXES);
presenter.setOneWayConversionEnabled(false);
presenter.setShowDuplicates(true);
-
+
return view;
}
-
+
/**
* Simulates an expression conversion operation, and ensures it works
* properly.
@@ -57,18 +57,18 @@ class TabbedViewTest {
@Test
void testExpressionConversion() {
final var view = setupView();
-
+
// prepare for unit conversion
view.masterPane.setSelectedIndex(1);
view.fromEntry.setText("250.0 inch");
view.toEntry.setText("metre");
-
+
view.convertExpressionButton.doClick();
-
+
// check result of conversion
assertEquals("250.0 inch = 6.350 metre", view.expressionOutput.getText());
}
-
+
/**
* Simulates a unit conversion operation, and ensures it works properly.
*
@@ -78,18 +78,18 @@ class TabbedViewTest {
@Test
void testUnitConversion() {
final var view = setupView();
-
+
// prepare for unit conversion
view.masterPane.setSelectedIndex(0);
view.dimensionSelector.setSelectedItem("Length");
view.fromSearch.getSearchList().setSelectedValue("inch", true);
view.toSearch.getSearchList().setSelectedValue("metre", true);
view.valueInput.setText("250.0");
-
+
view.convertUnitButton.doClick();
-
+
// check result of conversion
assertEquals("250.0 inch = 6.350 metre", view.unitOutput.getText());
}
-
+
}
diff --git a/src/test/resources/test-dimensionfile-valid1.txt b/src/test/resources/test-dimensionfile-valid1.txt
index fc6a426..d51ffe0 100644
--- a/src/test/resources/test-dimensionfile-valid1.txt
+++ b/src/test/resources/test-dimensionfile-valid1.txt
@@ -3,10 +3,4 @@ MASS !
TIME !
ENERGY MASS * LENGTH^2 / TIME^2
-POWER ENERGY / TIME
-
-# doesn't work, but would require major changes to fix properly
-# for now, just don't use brackets in dimension expressions
-# (note that the unit/prefix expressions use a complete hack
-# to enable this, one that doesn't work for dimensions)
-# POWER MASS * (LENGTH / TIME)^2 / TIME \ No newline at end of file
+POWER MASS * (LENGTH / TIME)^2 / TIME \ No newline at end of file