summaryrefslogtreecommitdiff
path: root/src/unitConverter
diff options
context:
space:
mode:
Diffstat (limited to 'src/unitConverter')
-rwxr-xr-xsrc/unitConverter/UnitsDatabase.java584
-rwxr-xr-xsrc/unitConverter/converterGUI/DelegateListModel.java232
-rwxr-xr-xsrc/unitConverter/converterGUI/FilterComparator.java93
-rwxr-xr-xsrc/unitConverter/converterGUI/GridBagBuilder.java479
-rwxr-xr-xsrc/unitConverter/converterGUI/UnitConverterGUI.java671
-rw-r--r--src/unitConverter/converterGUI/package-info.java (renamed from src/unitConverter/converter/package-info.java)2
-rwxr-xr-xsrc/unitConverter/dimension/BaseDimension.java5
-rwxr-xr-xsrc/unitConverter/dimension/OtherBaseDimension.java2
-rwxr-xr-xsrc/unitConverter/dimension/SIBaseDimension.java2
-rwxr-xr-xsrc/unitConverter/dimension/StandardDimensions.java1
-rwxr-xr-xsrc/unitConverter/dimension/UnitDimension.java11
-rwxr-xr-xsrc/unitConverter/dimension/UnitDimensionTest.java21
-rw-r--r--src/unitConverter/unit/AbstractUnit.java172
-rwxr-xr-xsrc/unitConverter/unit/BaseUnit.java180
-rwxr-xr-xsrc/unitConverter/unit/DefaultUnitPrefix.java67
-rw-r--r--src/unitConverter/unit/LinearUnit.java184
-rwxr-xr-xsrc/unitConverter/unit/NonlinearUnits.java57
-rw-r--r--src/unitConverter/unit/SI.java74
-rwxr-xr-xsrc/unitConverter/unit/SIPrefix.java54
-rwxr-xr-xsrc/unitConverter/unit/Unit.java9
-rwxr-xr-xsrc/unitConverter/unit/UnitPrefix.java33
-rwxr-xr-xsrc/unitConverter/unit/UnitSystem.java24
-rwxr-xr-xsrc/unitConverter/unit/UnitTest.java47
23 files changed, 3000 insertions, 4 deletions
diff --git a/src/unitConverter/UnitsDatabase.java b/src/unitConverter/UnitsDatabase.java
new file mode 100755
index 0000000..4816db1
--- /dev/null
+++ b/src/unitConverter/UnitsDatabase.java
@@ -0,0 +1,584 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import unitConverter.dimension.UnitDimension;
+import unitConverter.unit.AbstractUnit;
+import unitConverter.unit.BaseUnit;
+import unitConverter.unit.DefaultUnitPrefix;
+import unitConverter.unit.LinearUnit;
+import unitConverter.unit.SI;
+import unitConverter.unit.Unit;
+import unitConverter.unit.UnitPrefix;
+
+/**
+ * A database of units.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+public final class UnitsDatabase {
+ /**
+ * The units in this system.
+ *
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+ private final Map<String, Unit> units;
+
+ /**
+ * The unit prefixes in this system.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private final Map<String, UnitPrefix> prefixes;
+
+ /**
+ * Creates the {@code UnitsDatabase}.
+ *
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public UnitsDatabase() {
+ this.units = new HashMap<>();
+ this.prefixes = new HashMap<>();
+ }
+
+ /**
+ * Adds all units from a file, using data from the database to parse them.
+ * <p>
+ * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
+ * any number of tab characters.
+ * <p>
+ * <p>
+ * Allowed exceptions:
+ * <ul>
+ * <li>Any line that begins with the '#' character is considered a comment and ignored.</li>
+ * <li>Blank lines are also ignored</li>
+ * <li>If an expression consists of a single exclamation point, instead of parsing it, this method will search the
+ * database for an existing unit. If no unit is found, an IllegalArgumentException is thrown. This is used to define
+ * initial units and ensure that the database contains them.</li>
+ * </ul>
+ *
+ * @param file
+ * file to read
+ * @throws IllegalArgumentException
+ * if the file cannot be parsed, found or read
+ * @throws NullPointerException
+ * if file is null
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public void addAllFromFile(final File file) {
+ Objects.requireNonNull(file, "file must not be null.");
+ try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
+ // while the reader has lines to read, read a line, then parse it, then add it
+ long lineCounter = 0;
+ while (reader.ready()) {
+ final String line = reader.readLine();
+ lineCounter++;
+
+ // ignore lines that start with a # sign - they're comments
+ if (line.startsWith("#") || line.isEmpty()) {
+ continue;
+ }
+
+ // divide line into name and expression
+ final String[] parts = line.split("\t");
+ if (parts.length < 2)
+ throw new IllegalArgumentException(String.format(
+ "Lines must consist of a unit name and its definition, separated by tab(s) (line %d).",
+ lineCounter));
+ final String name = parts[0];
+ final String expression = parts[parts.length - 1];
+
+ if (name.endsWith(" ")) {
+ System.err.printf("Warning - line %d's unit name ends in a space", lineCounter);
+ }
+
+ // if expression is "!", search for an existing unit
+ // if no unit found, throw an error
+ if (expression.equals("!")) {
+ if (!this.containsUnitName(name))
+ throw new IllegalArgumentException(
+ String.format("! used but no unit found (line %d).", lineCounter));
+ } else {
+ if (name.endsWith("-")) {
+ final UnitPrefix prefix;
+ try {
+ prefix = this.getPrefixFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
+ }
+ this.addPrefix(name.substring(0, name.length() - 1), prefix);
+ } else {
+ // it's a unit, get the unit
+ final Unit unit;
+ try {
+ unit = this.getUnitFromExpression(expression);
+ } catch (final IllegalArgumentException e) {
+ System.err.printf("Parsing error on line %d:%n", lineCounter);
+ throw e;
+ }
+ AbstractUnit.incrementUnitCounter();
+ if (unit instanceof BaseUnit) {
+ AbstractUnit.incrementBaseUnitCounter();
+ }
+ this.addUnit(name, unit);
+ }
+ }
+ }
+ } catch (final FileNotFoundException e) {
+ throw new IllegalArgumentException("Could not find file " + file, e);
+ } catch (final IOException e) {
+ throw new IllegalArgumentException("Could not read file " + file, e);
+ }
+ }
+
+ /**
+ * Adds a unit prefix to the database using a custom name
+ *
+ * @param name
+ * prefix's name
+ * @param prefix
+ * prefix to add
+ * @throws NullPointerException
+ * if name or unit is null
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public void addPrefix(final String name, final UnitPrefix prefix) {
+ this.prefixes.put(Objects.requireNonNull(name, "name must not be null."),
+ Objects.requireNonNull(prefix, "prefix must not be null."));
+ }
+
+ /**
+ * Adds a unit to the database using a custom name
+ *
+ * @param name
+ * unit's name
+ * @param unit
+ * unit to add
+ * @throws NullPointerException
+ * if unit is null
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public void addUnit(final String name, final Unit unit) {
+ this.units.put(name, Objects.requireNonNull(unit, "unit must not be null."));
+ }
+
+ /**
+ * Tests if the database has a unit with this name, ignoring prefixes
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public boolean containsPrefixlessUnitName(final String name) {
+ return this.units.containsKey(name);
+ }
+
+ /**
+ * Tests if the database has a unit prefix with this name
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public boolean containsPrefixName(final String name) {
+ return this.prefixes.containsKey(name);
+ }
+
+ /**
+ * Tests if the database has a unit with this name, taking prefixes into consideration
+ *
+ * @param name
+ * name to test
+ * @return if database contains name
+ * @since 2019-01-13
+ * @since v0.1.0
+ */
+ public boolean containsUnitName(final String name) {
+ // check for prefixes
+ for (final String prefixName : this.prefixNameSet()) {
+ if (name.startsWith(prefixName))
+ if (this.containsUnitName(name.substring(prefixName.length())))
+ return true;
+ }
+ return this.units.containsKey(name);
+ }
+
+ /**
+ * Gets a unit prefix from the database from its name
+ *
+ * @param name
+ * prefix's name
+ * @return prefix
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public UnitPrefix getPrefix(final String name) {
+ return this.prefixes.get(name);
+ }
+
+ /**
+ * Gets a unit prefix from a prefix expression
+ * <p>
+ * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
+ * another prefix
+ * </p>
+ *
+ * @param expression
+ * expression to input
+ * @return prefix
+ * @throws IllegalArgumentException
+ * if expression cannot be parsed
+ * @throws NullPointerException
+ * if any argument is null
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public UnitPrefix getPrefixFromExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ try {
+ return new DefaultUnitPrefix(Double.parseDouble(expression));
+ } catch (final NumberFormatException e) {
+ if (expression.contains("^")) {
+ final String[] baseAndExponent = expression.split("\\^");
+
+ final double base;
+ try {
+ base = Double.parseDouble(baseAndExponent[0]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Base of exponientation must be a number.");
+ }
+
+ final int exponent;
+ try {
+ exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponent must be an integer.");
+ }
+
+ return new DefaultUnitPrefix(Math.pow(base, exponent));
+ } else {
+ if (!this.containsPrefixName(expression))
+ throw new IllegalArgumentException("Unrecognized prefix name \"" + expression + "\".");
+ return this.getPrefix(expression);
+ }
+ }
+ }
+
+ /**
+ * Gets a unit from the database from its name, ignoring prefixes.
+ *
+ * @param name
+ * unit's name
+ * @return unit
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public Unit getPrefixlessUnit(final String name) {
+ return this.units.get(name);
+ }
+
+ /**
+ * Gets a unit from the database from its name, looking for prefixes.
+ *
+ * @param name
+ * unit's name
+ * @return unit
+ * @since 2019-01-10
+ * @since v0.1.0
+ */
+ public Unit getUnit(final String name) {
+ if (name.contains("^")) {
+ final String[] baseAndExponent = name.split("\\^");
+
+ LinearUnit base;
+ try {
+ base = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(baseAndExponent[0]));
+ } catch (final NumberFormatException e2) {
+ final Unit unit = this.getUnit(baseAndExponent[0]);
+ if (unit instanceof LinearUnit) {
+ base = (LinearUnit) unit;
+ } else if (unit instanceof BaseUnit) {
+ base = ((BaseUnit) unit).asLinearUnit();
+ } else
+ throw new IllegalArgumentException("Base of exponientation must be a linear or base unit.");
+ }
+
+ final int exponent;
+ try {
+ exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponent must be an integer.");
+ }
+
+ final LinearUnit exponentiated = base.toExponent(exponent);
+ if (exponentiated.getConversionFactor() == 1)
+ return exponentiated.getSystem().getBaseUnit(exponentiated.getDimension());
+ else
+ return exponentiated;
+ } else {
+ for (final String prefixName : this.prefixNameSet()) {
+ // check for a prefix
+ if (name.startsWith(prefixName)) {
+ // prefix found! Make sure what comes after it is actually a unit!
+ final String prefixless = name.substring(prefixName.length());
+ if (this.containsUnitName(prefixless)) {
+ // yep, it's a proper prefix! Get the unit!
+ final Unit unit = this.getUnit(prefixless);
+ final UnitPrefix prefix = this.getPrefix(prefixName);
+
+ // Prefixes only work with linear and base units, so make sure it's one of those
+ if (unit instanceof LinearUnit) {
+ final LinearUnit linearUnit = (LinearUnit) unit;
+ return linearUnit.times(prefix.getMultiplier());
+ } else if (unit instanceof BaseUnit) {
+ final BaseUnit baseUnit = (BaseUnit) unit;
+ return baseUnit.times(prefix.getMultiplier());
+ }
+ }
+ }
+ }
+ return this.units.get(name);
+ }
+ }
+
+ /**
+ * Uses the database's unit data to parse an expression into a unit
+ * <p>
+ * The expression is a series of any of the following:
+ * <ul>
+ * <li>The name of a unit, which multiplies or divides the result based on preceding operators</li>
+ * <li>The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
+ * other is equivalent to multiplication)</li>
+ * <li>The operator '^' which exponentiates. Exponents must be integers.</li>
+ * <li>A number which is multiplied or divided</li>
+ * </ul>
+ * This method only works with linear units.
+ *
+ * @param expression
+ * expression to parse
+ * @throws IllegalArgumentException
+ * if the expression cannot be parsed
+ * @throws NullPointerException
+ * if any argument is null
+ * @since 2019-01-07
+ * @since v0.1.0
+ */
+ public Unit getUnitFromExpression(final String expression) {
+ Objects.requireNonNull(expression, "expression must not be null.");
+
+ // parse the expression
+ // start with an "empty" unit then apply operations on it
+ LinearUnit unit = SI.SI.getBaseUnit(UnitDimension.EMPTY).asLinearUnit();
+ boolean dividing = false;
+
+ // if I'm just creating an alias, just create one instead of going through the parsing process
+ if (!expression.contains(" ") && !expression.contains("*") && !expression.contains("/")
+ && !expression.contains("(") && !expression.contains(")") && !expression.contains("^")) {
+ try {
+ return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(expression));
+ } catch (final NumberFormatException e) {
+ if (!this.containsUnitName(expression))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + expression + "\".");
+ return this.getUnit(expression);
+ }
+ }
+
+ // \\* means "asterisk", * is reserved
+ for (final String part : expression.replaceAll("\\*", " \\* ").replaceAll("/", " / ").replaceAll(" \\^", "\\^")
+ .replaceAll("\\^ ", "\\^").split(" ")) {
+ if ("".equals(part)) {
+ continue;
+ }
+ // "unit1 unit2" is the same as "unit1 * unit2", so multiplication signs do nothing
+ if ("*".equals(part)) {
+ continue;
+ }
+ // When I reach a division sign, don't parse a unit, instead tell myself I'm going to divide the next
+ // thing
+ if ("/".equals(part) || "per".equals(part)) {
+ dividing = true;
+ continue;
+ }
+
+ try {
+ final double partAsNumber = Double.parseDouble(part); // if this works, it's a number
+ // this code should not throw any exceptions, so I'm going to throw AssertionErrors if it does
+ try {
+ if (dividing) {
+ unit = unit.dividedBy(partAsNumber);
+ dividing = false;
+ } else {
+ unit = unit.times(partAsNumber);
+ }
+ } catch (final Exception e) {
+ throw new AssertionError(e);
+ }
+ } catch (final NumberFormatException e) {
+ // it's a unit, try that
+
+ if (part.contains("(") && part.endsWith(")")) {
+ // the unitsfile is looking for a nonlinear unit
+ final String[] unitAndValue = part.split("\\(");
+
+ // this will work because I've checked that it contains a (
+ final String unitName = unitAndValue[0];
+ final String valueStringWithBracket = unitAndValue[unitAndValue.length - 1];
+ final String valueString = valueStringWithBracket.substring(0, valueStringWithBracket.length() - 1);
+ final double value;
+
+ // try to get the value - else throw an error
+ try {
+ value = Double.parseDouble(valueString);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Unparseable value " + valueString);
+ }
+
+ // get this unit in a linear form
+ if (!this.containsPrefixlessUnitName(unitName))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
+ final Unit partUnit = this.getPrefixlessUnit(unitName);
+ final LinearUnit multiplier = partUnit.getBase().times(partUnit.convertToBase(value));
+
+ // finally, add it to the expression
+ if (dividing) {
+ unit = unit.dividedBy(multiplier);
+ dividing = false;
+ } else {
+ unit = unit.times(multiplier);
+ }
+ } else {
+ // check for exponientation
+ if (part.contains("^")) {
+ final String[] valueAndExponent = part.split("\\^");
+ // this will always work because of the contains check
+ final String valueString = valueAndExponent[0];
+ final String exponentString = valueAndExponent[valueAndExponent.length - 1];
+
+ LinearUnit value;
+
+ // first, try to get the value
+ try {
+ final double valueAsNumber = Double.parseDouble(valueString);
+
+ value = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(valueAsNumber);
+ } catch (final NumberFormatException e2) {
+
+ // look for a unit
+ if (!this.containsUnitName(valueString))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + valueString + "\".");
+ final Unit valueUnit = this.getUnit(valueString);
+
+ // try to turn the value into a linear unit
+ if (valueUnit instanceof LinearUnit) {
+ value = (LinearUnit) valueUnit;
+ } else if (valueUnit instanceof BaseUnit) {
+ value = ((BaseUnit) valueUnit).asLinearUnit();
+ } else
+ throw new IllegalArgumentException("Only linear and base units can be exponientated.");
+ }
+
+ // now, try to get the exponent
+ final int exponent;
+ try {
+ exponent = Integer.parseInt(exponentString);
+ } catch (final NumberFormatException e2) {
+ throw new IllegalArgumentException("Exponents must be integers.");
+ }
+
+ final LinearUnit exponientated = value.toExponent(exponent);
+
+ if (dividing) {
+ unit = unit.dividedBy(exponientated);
+ dividing = false;
+ } else {
+ unit = unit.times(exponientated);
+ }
+ } else {
+ // no exponent - look for a unit
+ // the unitsfile is looking for a linear unit
+ if (!this.containsUnitName(part))
+ throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\".");
+ Unit other = this.getUnit(part);
+ if (other instanceof BaseUnit) {
+ other = ((BaseUnit) other).asLinearUnit();
+ }
+ if (other instanceof LinearUnit) {
+ if (dividing) {
+ unit = unit.dividedBy((LinearUnit) other);
+ dividing = false;
+ } else {
+ unit = unit.times((LinearUnit) other);
+ }
+ } else
+ throw new IllegalArgumentException(
+ "Only linear or base units can be multiplied and divided. If you want to use a non-linear unit, try the format UNITNAME(VALUE).");
+ }
+ }
+ }
+ }
+
+ // replace conversion-factor-1 linear units with base units
+ // this improves the autogenerated names, allowing them to use derived SI names
+ if (unit != null && unit.getConversionFactor() == 1)
+ return unit.getSystem().getBaseUnit(unit.getDimension());
+ else
+ return unit;
+ }
+
+ /**
+ * @return an immutable set of all of the unit names in this database, ignoring prefixes
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public Set<String> prefixlessUnitNameSet() {
+ return Collections.unmodifiableSet(this.units.keySet());
+ }
+
+ /**
+ * @return an immutable set of all of the prefix names in this database
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public Set<String> prefixNameSet() {
+ return Collections.unmodifiableSet(this.prefixes.keySet());
+ }
+}
diff --git a/src/unitConverter/converterGUI/DelegateListModel.java b/src/unitConverter/converterGUI/DelegateListModel.java
new file mode 100755
index 0000000..0e9b342
--- /dev/null
+++ b/src/unitConverter/converterGUI/DelegateListModel.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.converterGUI;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.swing.AbstractListModel;
+
+/**
+ * A list model that delegates to a list.
+ * <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.
+ * </p>
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+final class DelegateListModel<E> extends AbstractListModel<E> implements List<E> {
+ /**
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private static final long serialVersionUID = 8985494428224810045L;
+
+ /**
+ * The list that this model is a delegate to.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private final List<E> delegate;
+
+ /**
+ * Creates the {@code DelegateListModel}.
+ *
+ * @param delegate
+ * list to delegate
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public DelegateListModel(final List<E> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean add(final E element) {
+ final int index = this.delegate.size();
+ final boolean success = this.delegate.add(element);
+ this.fireIntervalAdded(this, index, index);
+ return success;
+ }
+
+ @Override
+ public void add(final int index, final E element) {
+ this.delegate.add(index, element);
+ this.fireIntervalAdded(this, index, index);
+ }
+
+ @Override
+ public boolean addAll(final Collection<? extends E> c) {
+ boolean changed = false;
+ for (final E e : c) {
+ if (this.add(e)) {
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public boolean addAll(final int index, final Collection<? extends E> c) {
+ for (final E e : c) {
+ this.add(index, e);
+ }
+ return !c.isEmpty(); // Since this is a list, it will always change if c has elements.
+ }
+
+ @Override
+ public void clear() {
+ final int oldSize = this.delegate.size();
+ this.delegate.clear();
+ if (oldSize >= 1) {
+ this.fireIntervalRemoved(this, 0, oldSize - 1);
+ }
+ }
+
+ @Override
+ public boolean contains(final Object elem) {
+ return this.delegate.contains(elem);
+ }
+
+ @Override
+ public boolean containsAll(final Collection<?> c) {
+ for (final Object e : c) {
+ if (!c.contains(e))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public E get(final int index) {
+ return this.delegate.get(index);
+ }
+
+ @Override
+ public E getElementAt(final int index) {
+ return this.delegate.get(index);
+ }
+
+ @Override
+ public int getSize() {
+ return this.delegate.size();
+ }
+
+ @Override
+ public int indexOf(final Object elem) {
+ return this.delegate.indexOf(elem);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.delegate.isEmpty();
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return this.delegate.iterator();
+ }
+
+ @Override
+ public int lastIndexOf(final Object elem) {
+ return this.delegate.lastIndexOf(elem);
+ }
+
+ @Override
+ public ListIterator<E> listIterator() {
+ return this.delegate.listIterator();
+ }
+
+ @Override
+ public ListIterator<E> listIterator(final int index) {
+ return this.delegate.listIterator(index);
+ }
+
+ @Override
+ public E remove(final int index) {
+ final E returnValue = this.delegate.get(index);
+ this.delegate.remove(index);
+ this.fireIntervalRemoved(this, index, index);
+ return returnValue;
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ final int index = this.delegate.indexOf(o);
+ final boolean returnValue = this.delegate.remove(o);
+ this.fireIntervalRemoved(this, index, index);
+ return returnValue;
+ }
+
+ @Override
+ public boolean removeAll(final Collection<?> c) {
+ boolean changed = false;
+ for (final Object e : c) {
+ if (this.remove(e)) {
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public boolean retainAll(final Collection<?> c) {
+ final int oldSize = this.size();
+ final boolean returnValue = this.delegate.retainAll(c);
+ this.fireIntervalRemoved(this, this.size(), oldSize - 1);
+ return returnValue;
+ }
+
+ @Override
+ public E set(final int index, final E element) {
+ final E returnValue = this.delegate.get(index);
+ this.delegate.set(index, element);
+ this.fireContentsChanged(this, index, index);
+ return returnValue;
+ }
+
+ @Override
+ public int size() {
+ return this.delegate.size();
+ }
+
+ @Override
+ public List<E> subList(final int fromIndex, final int toIndex) {
+ return this.delegate.subList(fromIndex, toIndex);
+ }
+
+ @Override
+ public Object[] toArray() {
+ return this.delegate.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(final T[] a) {
+ return this.delegate.toArray(a);
+ }
+
+ @Override
+ public String toString() {
+ return this.delegate.toString();
+ }
+}
diff --git a/src/unitConverter/converterGUI/FilterComparator.java b/src/unitConverter/converterGUI/FilterComparator.java
new file mode 100755
index 0000000..27ec3ab
--- /dev/null
+++ b/src/unitConverter/converterGUI/FilterComparator.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.converterGUI;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * A comparator that compares strings using a filter.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+public final class FilterComparator implements Comparator<String> {
+ /**
+ * The filter that the comparator is filtered by.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final String filter;
+ /**
+ * The comparator to use if the arguments are otherwise equal.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final Comparator<String> comparator;
+
+ /**
+ * Creates the {@code FilterComparator}.
+ *
+ * @param filter
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public FilterComparator(final String filter) {
+ this(filter, null);
+ }
+
+ /**
+ * Creates the {@code FilterComparator}.
+ *
+ * @param filter
+ * string to filter by
+ * @param comparator
+ * comparator to fall back to if all else fails, null is compareTo.
+ * @since 2019-01-15
+ * @since v0.1.0
+ * @throws NullPointerException
+ * if filter is null
+ */
+ public FilterComparator(final String filter, final Comparator<String> comparator) {
+ this.filter = Objects.requireNonNull(filter, "filter must not be null.");
+ this.comparator = comparator;
+ }
+
+ @Override
+ public int compare(final String arg0, final String arg1) {
+ // elements that start with the filter always go first
+ if (arg0.startsWith(this.filter) && !arg1.startsWith(this.filter))
+ return -1;
+ else if (!arg0.startsWith(this.filter) && arg1.startsWith(this.filter))
+ return 1;
+
+ // elements that contain the filter but don't start with them go next
+ if (arg0.contains(this.filter) && !arg1.contains(this.filter))
+ return -1;
+ else if (!arg0.contains(this.filter) && !arg1.contains(this.filter))
+ return 1;
+
+ // other elements go last
+ if (this.comparator == null)
+ return arg0.compareTo(arg1);
+ else
+ return this.comparator.compare(arg0, arg1);
+ }
+}
diff --git a/src/unitConverter/converterGUI/GridBagBuilder.java b/src/unitConverter/converterGUI/GridBagBuilder.java
new file mode 100755
index 0000000..e036677
--- /dev/null
+++ b/src/unitConverter/converterGUI/GridBagBuilder.java
@@ -0,0 +1,479 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.converterGUI;
+
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+
+/**
+ * A builder for Java's {@link java.awt.GridBagConstraints} class.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+final class GridBagBuilder {
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridx} property.
+ * <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.
+ * <p>
+ * The default value is <code>RELATIVE</code>. <code>gridx</code> should be a non-negative value.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridy
+ * @see java.awt.ComponentOrientation
+ */
+ private final int gridx;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridy} property.
+ * <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.
+ * <p>
+ * The default value is <code>RELATIVE</code>. <code>gridy</code> should be a non-negative value.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridx
+ */
+ private final int gridy;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridwidth} property.
+ * <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.
+ * <p>
+ * <code>gridwidth</code> should be non-negative and the default value is 1.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridheight
+ */
+ private final int gridwidth;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code gridheight} property.
+ * <p>
+ * 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.
+ * <p>
+ * <code>gridheight</code> should be a non-negative value and the default value is 1.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#gridwidth
+ */
+ private final int gridheight;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code weightx} property.
+ * <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.
+ * <p>
+ * 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.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#weighty
+ */
+ private double weightx;
+
+ /**
+ * The built {@code GridBagConstraints}'s {@code weighty} property.
+ * <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.
+ * <p>
+ * 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.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#weightx
+ */
+ private double weighty;
+
+ /**
+ * 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.
+ * <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
+ * <code>CENTER</code>.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.ComponentOrientation
+ */
+ private int anchor;
+
+ /**
+ * 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.
+ * <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>BOTH</code>: Make the component fill its display area entirely.
+ * </ul>
+ * <p>
+ * The default value is <code>NONE</code>.
+ *
+ * @serial
+ * @see #clone()
+ */
+ private int fill;
+
+ /**
+ * 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.
+ * <p>
+ * The default value is <code>new Insets(0, 0, 0, 0)</code>.
+ *
+ * @serial
+ * @see #clone()
+ */
+ private Insets insets;
+
+ /**
+ * 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.
+ * <p>
+ * The default value is <code>0</code>.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#ipady
+ */
+ private int ipadx;
+
+ /**
+ * 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.
+ * <p>
+ * The default value is 0.
+ *
+ * @serial
+ * @see #clone()
+ * @see java.awt.GridBagConstraints#ipadx
+ */
+ private int ipady;
+
+ /**
+ * @param gridx
+ * x position
+ * @param gridy
+ * y position
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder(final int gridx, final int gridy) {
+ this(gridx, gridy, 1, 1);
+ }
+
+ /**
+ * @param gridx
+ * x position
+ * @param gridy
+ * y position
+ * @param gridwidth
+ * number of cells occupied horizontally
+ * @param gridheight
+ * number of cells occupied vertically
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight) {
+ this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0);
+ }
+
+ /**
+ * @param gridx
+ * x position
+ * @param gridy
+ * y position
+ * @param gridwidth
+ * number of cells occupied horizontally
+ * @param gridheight
+ * number of cells occupied vertically
+ * @param weightx
+ * @param weighty
+ * @param anchor
+ * @param fill
+ * @param insets
+ * @param ipadx
+ * @param ipady
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight,
+ final double weightx, final double weighty, final int anchor, final int fill, final Insets insets,
+ final int ipadx, final int ipady) {
+ super();
+ this.gridx = gridx;
+ this.gridy = gridy;
+ this.gridwidth = gridwidth;
+ this.gridheight = gridheight;
+ this.weightx = weightx;
+ this.weighty = weighty;
+ this.anchor = anchor;
+ this.fill = fill;
+ this.insets = (Insets) insets.clone();
+ this.ipadx = ipadx;
+ this.ipady = ipady;
+ }
+
+ /**
+ * @return {@code GridBagConstraints} created by this builder
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagConstraints build() {
+ return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, this.gridheight, this.weightx,
+ this.weighty, this.anchor, this.fill, this.insets, this.ipadx, this.ipady);
+ }
+
+ /**
+ * @return anchor
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getAnchor() {
+ return this.anchor;
+ }
+
+ /**
+ * @return fill
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getFill() {
+ return this.fill;
+ }
+
+ /**
+ * @return gridheight
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridheight() {
+ return this.gridheight;
+ }
+
+ /**
+ * @return gridwidth
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridwidth() {
+ return this.gridwidth;
+ }
+
+ /**
+ * @return gridx
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridx() {
+ return this.gridx;
+ }
+
+ /**
+ * @return gridy
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getGridy() {
+ return this.gridy;
+ }
+
+ /**
+ * @return insets
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public Insets getInsets() {
+ return this.insets;
+ }
+
+ /**
+ * @return ipadx
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getIpadx() {
+ return this.ipadx;
+ }
+
+ /**
+ * @return ipady
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public int getIpady() {
+ return this.ipady;
+ }
+
+ /**
+ * @return weightx
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public double getWeightx() {
+ return this.weightx;
+ }
+
+ /**
+ * @return weighty
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public double getWeighty() {
+ return this.weighty;
+ }
+
+ /**
+ * @param anchor
+ * anchor to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setAnchor(final int anchor) {
+ this.anchor = anchor;
+ return this;
+ }
+
+ /**
+ * @param fill
+ * fill to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setFill(final int fill) {
+ this.fill = fill;
+ return this;
+ }
+
+ /**
+ * @param insets
+ * insets to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setInsets(final Insets insets) {
+ this.insets = insets;
+ return this;
+ }
+
+ /**
+ * @param ipadx
+ * ipadx to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setIpadx(final int ipadx) {
+ this.ipadx = ipadx;
+ return this;
+ }
+
+ /**
+ * @param ipady
+ * ipady to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setIpady(final int ipady) {
+ this.ipady = ipady;
+ return this;
+ }
+
+ /**
+ * @param weightx
+ * weightx to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setWeightx(final double weightx) {
+ this.weightx = weightx;
+ return this;
+ }
+
+ /**
+ * @param weighty
+ * weighty to set
+ * @since 2018-11-30
+ * @since v0.1.0
+ */
+ public GridBagBuilder setWeighty(final double weighty) {
+ this.weighty = weighty;
+ return this;
+ }
+}
diff --git a/src/unitConverter/converterGUI/UnitConverterGUI.java b/src/unitConverter/converterGUI/UnitConverterGUI.java
new file mode 100755
index 0000000..b54e0da
--- /dev/null
+++ b/src/unitConverter/converterGUI/UnitConverterGUI.java
@@ -0,0 +1,671 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.converterGUI;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Predicate;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+
+import unitConverter.UnitsDatabase;
+import unitConverter.dimension.StandardDimensions;
+import unitConverter.dimension.UnitDimension;
+import unitConverter.unit.AbstractUnit;
+import unitConverter.unit.NonlinearUnits;
+import unitConverter.unit.SI;
+import unitConverter.unit.Unit;
+import unitConverter.unit.UnitPrefix;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+final class UnitConverterGUI {
+ private static class Presenter {
+ /** The presenter's associated view. */
+ private final View view;
+
+ /** The units known by the program. */
+ private final UnitsDatabase units;
+
+ /** The names of all of the units */
+ private final List<String> unitNames;
+
+ /** The names of all of the units, but filtered */
+ private final DelegateListModel<String> unitNamesFiltered;
+
+ /** The names of all of the prefixes */
+ private final List<String> prefixNames;
+
+ /** The names of all of the prefixes */
+ private final DelegateListModel<String> prefixNamesFiltered;
+
+ private final Comparator<String> prefixNameComparator;
+
+ private int significantFigures = 6;
+
+ /**
+ * Creates the presenter.
+ *
+ * @param view
+ * presenter's associated view
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ Presenter(final View view) {
+ this.view = view;
+
+ // load initial units
+ this.units = new UnitsDatabase();
+ this.units.addUnit("metre", SI.METRE);
+ this.units.addUnit("kilogram", SI.KILOGRAM);
+ this.units.addUnit("gram", SI.KILOGRAM.dividedBy(1000));
+ this.units.addUnit("second", SI.SECOND);
+ this.units.addUnit("ampere", SI.AMPERE);
+ this.units.addUnit("kelvin", SI.KELVIN);
+ this.units.addUnit("mole", SI.MOLE);
+ this.units.addUnit("candela", SI.CANDELA);
+ this.units.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION));
+ this.units.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY));
+ // nonlinear units - must be loaded manually
+ this.units.addUnit("tempCelsius", NonlinearUnits.CELSIUS);
+ this.units.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT);
+
+ this.units.addAllFromFile(new File("unitsfile.txt"));
+
+ // a comparator that can be used to compare prefix names
+ // any name that does not exist is less than a name that does.
+ // otherwise, they are compared by value
+ this.prefixNameComparator = (o1, o2) -> {
+ if (!Presenter.this.units.containsPrefixName(o1))
+ return -1;
+ else if (!Presenter.this.units.containsPrefixName(o2))
+ return 1;
+
+ final UnitPrefix p1 = Presenter.this.units.getPrefix(o1);
+ final UnitPrefix p2 = Presenter.this.units.getPrefix(o2);
+
+ if (p1.getMultiplier() < p2.getMultiplier())
+ return -1;
+ else if (p1.getMultiplier() > p2.getMultiplier())
+ return 1;
+
+ return o1.compareTo(o2);
+ };
+
+ this.unitNames = new ArrayList<>(this.units.prefixlessUnitNameSet());
+ this.unitNames.sort(null); // sorts it using Comparable
+
+ this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixlessUnitNameSet()));
+ this.unitNamesFiltered.sort(null); // sorts it using Comparable
+
+ this.prefixNames = new ArrayList<>(this.units.prefixNameSet());
+ this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator
+
+ this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet()));
+ this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator
+
+ System.out.printf("Successfully loaded %d units (%d base units)", AbstractUnit.getUnitCount(),
+ AbstractUnit.getBaseUnitCount());
+ }
+
+ /**
+ * Runs whenever the convert button is pressed.
+ *
+ * <p>
+ * Reads and parses a unit expression from the from and to boxes, then converts {@code from} to {@code to}. Any
+ * errors are shown in JOptionPanes.
+ * </p>
+ *
+ * @since 2019-01-26
+ * @since v0.1.0
+ */
+ public final void convert() {
+ final String fromUnitString = this.view.getFromText();
+ final String toUnitString = this.view.getToText();
+
+ // try to parse from
+ final Unit from;
+ try {
+ from = this.units.getUnitFromExpression(fromUnitString);
+ } catch (final IllegalArgumentException e) {
+ this.view.showErrorDialog("Parse Error", "Could not recognize text in From entry: " + e.getMessage());
+ return;
+ }
+
+ final double value;
+ // try to parse to
+ final Unit to;
+ try {
+ to = this.units.getUnitFromExpression(toUnitString);
+ } catch (final IllegalArgumentException e) {
+ this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage());
+ return;
+ }
+
+ // if I can't convert, leave
+ if (!from.canConvertTo(to)) {
+ this.view.showErrorDialog("Conversion Error",
+ String.format("Cannot convert between %s and %s", fromUnitString, toUnitString));
+ return;
+ }
+
+ value = to.convertFromBase(from.convertToBase(1));
+
+ // round value
+ final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures));
+ String output = bigValue.toString();
+
+ // remove trailing zeroes
+ if (output.contains(".")) {
+ while (output.endsWith("0")) {
+ output = output.substring(0, output.length() - 1);
+ }
+ if (output.endsWith(".")) {
+ output = output.substring(0, output.length() - 1);
+ }
+ }
+
+ this.view.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString));
+ }
+
+ /**
+ * Filters the filtered model for units
+ *
+ * @param filter
+ * filter to use
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final void filterFilteredPrefixModel(final Predicate<String> filter) {
+ this.prefixNamesFiltered.clear();
+ for (final String prefixName : this.prefixNames) {
+ if (filter.test(prefixName)) {
+ this.prefixNamesFiltered.add(prefixName);
+ }
+ }
+ }
+
+ /**
+ * Filters the filtered model for units
+ *
+ * @param filter
+ * filter to use
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final void filterFilteredUnitModel(final Predicate<String> filter) {
+ this.unitNamesFiltered.clear();
+ for (final String unitName : this.unitNames) {
+ if (filter.test(unitName)) {
+ this.unitNamesFiltered.add(unitName);
+ }
+ }
+ }
+
+ /**
+ * @return a list model of all of the unit keys
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public final ListModel<String> keyListModel() {
+ return this.unitNamesFiltered;
+ }
+
+ /**
+ * Runs whenever the prefix filter is changed.
+ * <p>
+ * Filters the prefix list then sorts it using a {@code FilterComparator}.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public final void prefixFilterUpdated() {
+ final String filter = this.view.getPrefixFilterText();
+ if (filter.equals("")) {
+ this.filterFilteredPrefixModel(t -> true);
+ } else {
+ this.filterFilteredPrefixModel(t -> t.contains(filter));
+ }
+ this.prefixNamesFiltered.sort(new FilterComparator(filter));
+ }
+
+ /**
+ * @return a list model of all of the prefix names
+ * @since 2019-01-15
+ */
+ public final ListModel<String> prefixNameListModel() {
+ return this.prefixNamesFiltered;
+ }
+
+ /**
+ * Runs whenever a prefix is selected in the viewer.
+ * <p>
+ * Shows its information in the text box to the right.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public final void prefixSelected() {
+ final int index = this.view.getPrefixListSelection();
+ if (index == -1)
+ return;
+ else {
+ final String prefixName = this.prefixNamesFiltered.get(index);
+ final UnitPrefix prefix = this.units.getPrefix(prefixName);
+
+ this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier()));
+ }
+ }
+
+ /**
+ * @param significantFigures
+ * new value of significantFigures
+ * @since 2019-01-15
+ */
+ public final void setSignificantFigures(final int significantFigures) {
+ this.significantFigures = significantFigures;
+ }
+
+ /**
+ * Runs whenever the unit filter is changed.
+ * <p>
+ * Filters the unit list then sorts it using a {@code FilterComparator}.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public final void unitFilterUpdated() {
+ final String filter = this.view.getUnitFilterText();
+ if (filter.equals("")) {
+ this.filterFilteredUnitModel(t -> true);
+ } else {
+ this.filterFilteredUnitModel(t -> t.contains(filter));
+ }
+ this.unitNamesFiltered.sort(new FilterComparator(filter));
+ }
+
+ /**
+ * Runs whenever a unit is selected in the viewer.
+ * <p>
+ * Shows its information in the text box to the right.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public void unitNameSelected() {
+ final int index = this.view.getUnitListSelection();
+ if (index == -1)
+ return;
+ else {
+ final String unitName = this.unitNamesFiltered.get(index);
+ final Unit unit = this.units.getUnit(unitName);
+
+ this.view.setUnitTextBoxText(unit.toString());
+ }
+ }
+ }
+
+ private static class View {
+ /** The view's frame. */
+ private final JFrame frame;
+ /** The view's associated presenter. */
+ private final Presenter presenter;
+
+ /** The list of unit names in the unit viewer */
+ private final JList<String> unitNameList;
+ /** The list of prefix names in the prefix viewer */
+ private final JList<String> prefixNameList;
+ /** The unit search box in the unit viewer */
+ private final JTextField unitFilterEntry;
+ /** The text box for unit data in the unit viewer */
+ private final JTextArea unitTextBox;
+ /** The prefix search box in the prefix viewer */
+ private final JTextField prefixFilterEntry;
+ /** The text box for prefix data in the prefix viewer */
+ private final JTextArea prefixTextBox;
+ /** The "From" entry in the conversion panel */
+ private final JTextField fromEntry;
+ /** The "To" entry in the conversion panel */
+ private final JTextField toEntry;
+ /** The output area in the conversion panel */
+ private final JTextArea output;
+
+ /**
+ * Creates the {@code View}.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public View() {
+ this.presenter = new Presenter(this);
+ this.frame = new JFrame("Unit Converter");
+ this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ // create the components
+ this.unitNameList = new JList<>(this.presenter.keyListModel());
+ this.prefixNameList = new JList<>(this.presenter.prefixNameListModel());
+ this.unitFilterEntry = new JTextField();
+ this.unitTextBox = new JTextArea();
+ this.prefixFilterEntry = new JTextField();
+ this.prefixTextBox = new JTextArea();
+ this.fromEntry = new JTextField();
+ this.toEntry = new JTextField();
+ this.output = new JTextArea(2, 32);
+
+ // create more components
+ this.initComponents();
+
+ this.frame.pack();
+ }
+
+ /**
+ * @return text in "From" box in converter panel
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public String getFromText() {
+ return this.fromEntry.getText();
+ }
+
+ /**
+ * @return text in prefix filter
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public String getPrefixFilterText() {
+ return this.prefixFilterEntry.getText();
+ }
+
+ /**
+ * @return index of selected prefix
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public int getPrefixListSelection() {
+ return this.prefixNameList.getSelectedIndex();
+ }
+
+ /**
+ * @return text in "To" box in converter panel
+ * @since 2019-01-26
+ * @since v0.1.0
+ */
+ public String getToText() {
+ return this.toEntry.getText();
+ }
+
+ /**
+ * @return text in unit filter
+ * @see javax.swing.text.JTextComponent#getText()
+ */
+ public String getUnitFilterText() {
+ return this.unitFilterEntry.getText();
+ }
+
+ /**
+ * @return index of selected unit
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public int getUnitListSelection() {
+ return this.unitNameList.getSelectedIndex();
+ }
+
+ /**
+ * Starts up the application.
+ *
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ public final void init() {
+ this.frame.setVisible(true);
+ }
+
+ /**
+ * Initializes the view's components.
+ *
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ private final void initComponents() {
+ final JPanel masterPanel = new JPanel();
+ this.frame.add(masterPanel);
+
+ masterPanel.setLayout(new BorderLayout());
+
+ { // pane with all of the tabs
+ final JTabbedPane masterPane = new JTabbedPane();
+ masterPanel.add(masterPane, BorderLayout.CENTER);
+
+ { // panel for unit conversion
+ final JPanel convertPanel = new JPanel();
+ masterPane.addTab("Convert Units", convertPanel);
+
+ convertPanel.setLayout(new GridLayout(5, 1));
+
+ { // panel for units to convert from
+ final JPanel fromPanel = new JPanel();
+ convertPanel.add(fromPanel);
+
+ fromPanel.setBorder(BorderFactory.createTitledBorder("From"));
+ fromPanel.setLayout(new GridLayout(1, 1));
+
+ { // entry for units
+ fromPanel.add(this.fromEntry);
+ }
+ }
+
+ { // panel for units to convert to
+ final JPanel toPanel = new JPanel();
+ convertPanel.add(toPanel);
+
+ toPanel.setBorder(BorderFactory.createTitledBorder("To"));
+ toPanel.setLayout(new GridLayout(1, 1));
+
+ { // entry for units
+ toPanel.add(this.toEntry);
+ }
+ }
+
+ { // button to convert
+ final JButton convertButton = new JButton("Convert!");
+ convertPanel.add(convertButton);
+
+ convertButton.addActionListener(e -> this.presenter.convert());
+ }
+
+ { // output of conversion
+ final JPanel outputPanel = new JPanel();
+ convertPanel.add(outputPanel);
+
+ outputPanel.setBorder(BorderFactory.createTitledBorder("Output"));
+ outputPanel.setLayout(new GridLayout(1, 1));
+
+ { // output
+ outputPanel.add(this.output);
+ this.output.setEditable(false);
+ }
+ }
+
+ { // panel for specifying precision
+ final JPanel sigDigPanel = new JPanel();
+ convertPanel.add(sigDigPanel);
+
+ sigDigPanel.setBorder(BorderFactory.createTitledBorder("Significant Digits"));
+
+ { // slider
+ final JSlider sigDigSlider = new JSlider(0, 12);
+ sigDigPanel.add(sigDigSlider);
+
+ sigDigSlider.setMajorTickSpacing(4);
+ sigDigSlider.setMinorTickSpacing(1);
+ sigDigSlider.setSnapToTicks(true);
+ sigDigSlider.setPaintTicks(true);
+ sigDigSlider.setPaintLabels(true);
+
+ sigDigSlider.addChangeListener(
+ e -> this.presenter.setSignificantFigures(sigDigSlider.getValue()));
+ }
+ }
+ }
+
+ { // panel to look up units
+ final JPanel unitLookupPanel = new JPanel();
+ masterPane.addTab("Unit Viewer", unitLookupPanel);
+
+ unitLookupPanel.setLayout(new GridLayout());
+
+ { // panel for listing and searching
+ final JPanel listPanel = new JPanel();
+ unitLookupPanel.add(listPanel);
+
+ listPanel.setLayout(new BorderLayout());
+
+ { // unit search box
+ listPanel.add(this.unitFilterEntry, BorderLayout.PAGE_START);
+ this.unitFilterEntry.addCaretListener(e -> this.presenter.unitFilterUpdated());
+ }
+
+ { // a list of units
+ listPanel.add(new JScrollPane(this.unitNameList), BorderLayout.CENTER);
+ this.unitNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
+ this.unitNameList.addListSelectionListener(e -> {
+ this.presenter.unitNameSelected();
+ });
+ }
+ }
+
+ { // the text box for unit's toString
+ unitLookupPanel.add(this.unitTextBox);
+ this.unitTextBox.setEditable(false);
+ this.unitTextBox.setLineWrap(true);
+ }
+ }
+
+ { // panel to look up prefixes
+ final JPanel prefixLookupPanel = new JPanel();
+ masterPane.addTab("Prefix Viewer", prefixLookupPanel);
+
+ prefixLookupPanel.setLayout(new GridLayout(1, 2));
+
+ { // panel for listing and seaching
+ final JPanel prefixListPanel = new JPanel();
+ prefixLookupPanel.add(prefixListPanel);
+
+ prefixListPanel.setLayout(new BorderLayout());
+
+ { // prefix search box
+ prefixListPanel.add(this.prefixFilterEntry, BorderLayout.PAGE_START);
+ this.prefixFilterEntry.addCaretListener(e -> this.presenter.prefixFilterUpdated());
+ }
+
+ { // a list of prefixes
+ prefixListPanel.add(new JScrollPane(this.prefixNameList), BorderLayout.CENTER);
+ this.prefixNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp
+ this.prefixNameList.addListSelectionListener(e -> {
+ this.presenter.prefixSelected();
+ });
+ }
+ }
+
+ { // the text box for prefix's toString
+ prefixLookupPanel.add(this.prefixTextBox);
+ this.unitTextBox.setEditable(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the text in the output of the conversion panel.
+ *
+ * @param text
+ * text to set
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public void setOutputText(final String text) {
+ this.output.setText(text);
+ }
+
+ /**
+ * Sets the text of the prefix text box.
+ *
+ * @param text
+ * text to set
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public void setPrefixTextBoxText(final String text) {
+ this.prefixTextBox.setText(text);
+ }
+
+ /**
+ * Sets the text of the unit text box.
+ *
+ * @param t
+ * text to set
+ * @see javax.swing.text.JTextComponent#setText(java.lang.String)
+ */
+ public void setUnitTextBoxText(final String t) {
+ this.unitTextBox.setText(t);
+ }
+
+ /**
+ * Shows an error dialog.
+ *
+ * @param title
+ * title of dialog
+ * @param message
+ * message in dialog
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public void showErrorDialog(final String title, final String message) {
+ JOptionPane.showMessageDialog(this.frame, message, title, JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ public static void main(final String[] args) {
+ new View().init();
+ }
+}
diff --git a/src/unitConverter/converter/package-info.java b/src/unitConverter/converterGUI/package-info.java
index 6148784..9f7fa57 100644
--- a/src/unitConverter/converter/package-info.java
+++ b/src/unitConverter/converterGUI/package-info.java
@@ -20,4 +20,4 @@
* @author Adrien Hopkins
* @since 2019-01-25
*/
-package unitConverter.converter; \ No newline at end of file
+package unitConverter.converterGUI; \ No newline at end of file
diff --git a/src/unitConverter/dimension/BaseDimension.java b/src/unitConverter/dimension/BaseDimension.java
index 6a727a9..0c09dce 100755
--- a/src/unitConverter/dimension/BaseDimension.java
+++ b/src/unitConverter/dimension/BaseDimension.java
@@ -21,17 +21,20 @@ package unitConverter.dimension;
*
* @author Adrien Hopkins
* @since 2018-12-22
+ * @since v0.1.0
*/
public interface BaseDimension {
/**
* @return the dimension's name
- * @since 2019-01-25
+ * @since 2018-12-22
+ * @since v0.1.0
*/
String getName();
/**
* @return a short string (usually one character) that represents this base dimension
* @since 2018-12-22
+ * @since v0.1.0
*/
String getSymbol();
}
diff --git a/src/unitConverter/dimension/OtherBaseDimension.java b/src/unitConverter/dimension/OtherBaseDimension.java
index 2ab19b3..8c6d25d 100755
--- a/src/unitConverter/dimension/OtherBaseDimension.java
+++ b/src/unitConverter/dimension/OtherBaseDimension.java
@@ -23,6 +23,7 @@ import java.util.Objects;
*
* @author Adrien Hopkins
* @since 2019-01-14
+ * @since v0.1.0
*/
public enum OtherBaseDimension implements BaseDimension {
INFORMATION("Info"), CURRENCY("$$");
@@ -36,6 +37,7 @@ public enum OtherBaseDimension implements BaseDimension {
* @param symbol
* dimension's symbol
* @since 2018-12-11
+ * @since v0.1.0
*/
private OtherBaseDimension(final String symbol) {
this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
diff --git a/src/unitConverter/dimension/SIBaseDimension.java b/src/unitConverter/dimension/SIBaseDimension.java
index b731b00..928d8d6 100755
--- a/src/unitConverter/dimension/SIBaseDimension.java
+++ b/src/unitConverter/dimension/SIBaseDimension.java
@@ -23,6 +23,7 @@ import java.util.Objects;
*
* @author Adrien Hopkins
* @since 2018-12-11
+ * @since v0.1.0
*/
public enum SIBaseDimension implements BaseDimension {
LENGTH("L"), MASS("M"), TIME("T"), ELECTRIC_CURRENT("I"), TEMPERATURE("\u0398"), // u0398 is the theta symbol
@@ -37,6 +38,7 @@ public enum SIBaseDimension implements BaseDimension {
* @param symbol
* dimension's symbol
* @since 2018-12-11
+ * @since v0.1.0
*/
private SIBaseDimension(final String symbol) {
this.symbol = Objects.requireNonNull(symbol, "symbol must not be null.");
diff --git a/src/unitConverter/dimension/StandardDimensions.java b/src/unitConverter/dimension/StandardDimensions.java
index c830f00..b3edb7d 100755
--- a/src/unitConverter/dimension/StandardDimensions.java
+++ b/src/unitConverter/dimension/StandardDimensions.java
@@ -21,6 +21,7 @@ package unitConverter.dimension;
*
* @author Adrien Hopkins
* @since 2018-12-11
+ * @since v0.1.0
*/
public final class StandardDimensions {
// base dimensions
diff --git a/src/unitConverter/dimension/UnitDimension.java b/src/unitConverter/dimension/UnitDimension.java
index ba2a750..40e5bbc 100755
--- a/src/unitConverter/dimension/UnitDimension.java
+++ b/src/unitConverter/dimension/UnitDimension.java
@@ -29,12 +29,14 @@ import java.util.Set;
*
* @author Adrien Hopkins
* @since 2018-12-11
+ * @since v0.1.0
*/
public final class UnitDimension {
/**
* The unit dimension where every exponent is zero
*
* @since 2018-12-12
+ * @since v0.1.0
*/
public static final UnitDimension EMPTY = new UnitDimension(new HashMap<>());
@@ -45,6 +47,7 @@ public final class UnitDimension {
* dimension to get
* @return unit dimension
* @since 2018-12-11
+ * @since v0.1.0
*/
public static final UnitDimension getBase(final BaseDimension dimension) {
final Map<BaseDimension, Integer> map = new HashMap<>();
@@ -56,6 +59,7 @@ public final class UnitDimension {
* The base dimensions that make up this dimension.
*
* @since 2018-12-11
+ * @since v0.1.0
*/
final Map<BaseDimension, Integer> exponents;
@@ -65,6 +69,7 @@ public final class UnitDimension {
* @param exponents
* base dimensions that make up this dimension
* @since 2018-12-11
+ * @since v0.1.0
*/
private UnitDimension(final Map<BaseDimension, Integer> exponents) {
this.exponents = new HashMap<>(exponents);
@@ -77,6 +82,7 @@ public final class UnitDimension {
* other dimension
* @return quotient of two dimensions
* @since 2018-12-11
+ * @since v0.1.0
*/
public UnitDimension dividedBy(final UnitDimension other) {
final Map<BaseDimension, Integer> map = new HashMap<>(this.exponents);
@@ -119,6 +125,7 @@ public final class UnitDimension {
/**
* @return a set of all of the base dimensions with non-zero exponents that make up this dimension.
* @since 2018-12-12
+ * @since v0.1.0
*/
public final Set<BaseDimension> getBaseSet() {
final Set<BaseDimension> dimensions = new HashSet<>();
@@ -140,6 +147,7 @@ public final class UnitDimension {
* dimension to check
* @return exponent for that dimension
* @since 2018-12-12
+ * @since v0.1.0
*/
public int getExponent(final BaseDimension dimension) {
return this.exponents.getOrDefault(dimension, 0);
@@ -153,6 +161,7 @@ public final class UnitDimension {
/**
* @return true if this dimension is a base, i.e. it has one exponent of one and no other nonzero exponents
* @since 2019-01-15
+ * @since v0.1.0
*/
public boolean isBase() {
int oneCount = 0;
@@ -174,6 +183,7 @@ public final class UnitDimension {
* other dimension
* @return product of two dimensions
* @since 2018-12-11
+ * @since v0.1.0
*/
public UnitDimension times(final UnitDimension other) {
final Map<BaseDimension, Integer> map = new HashMap<>(this.exponents);
@@ -196,6 +206,7 @@ public final class UnitDimension {
* exponent
* @return result of exponientation
* @since 2019-01-15
+ * @since v0.1.0
*/
public UnitDimension toExponent(final int exp) {
final Map<BaseDimension, Integer> map = new HashMap<>(this.exponents);
diff --git a/src/unitConverter/dimension/UnitDimensionTest.java b/src/unitConverter/dimension/UnitDimensionTest.java
index 603320b..86db1b8 100755
--- a/src/unitConverter/dimension/UnitDimensionTest.java
+++ b/src/unitConverter/dimension/UnitDimensionTest.java
@@ -30,22 +30,43 @@ import static unitConverter.dimension.StandardDimensions.VOLUME;
import org.junit.jupiter.api.Test;
/**
+ * Tests for {@link UnitDimension}.
+ *
* @author Adrien Hopkins
* @since 2018-12-12
+ * @since v0.1.0
*/
class UnitDimensionTest {
+ /**
+ * Tests {@link UnitDimension#equals}
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
@Test
void testEquals() {
assertEquals(LENGTH, LENGTH);
assertFalse(LENGTH.equals(QUANTITY));
}
+ /**
+ * Tests {@code UnitDimension}'s exponentiation
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
@Test
void testExponents() {
assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH));
assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH));
}
+ /**
+ * Tests {@code UnitDimension}'s multiplication and division.
+ *
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
@Test
void testMultiplicationAndDivision() {
assertEquals(AREA, LENGTH.times(LENGTH));
diff --git a/src/unitConverter/unit/AbstractUnit.java b/src/unitConverter/unit/AbstractUnit.java
new file mode 100644
index 0000000..24814e8
--- /dev/null
+++ b/src/unitConverter/unit/AbstractUnit.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+import java.util.Objects;
+
+import unitConverter.dimension.UnitDimension;
+
+/**
+ * The default abstract implementation of the {@code Unit} interface.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+public abstract class AbstractUnit implements Unit {
+ /**
+ * The number of units created, including base units.
+ *
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ private static long unitCount = 0;
+
+ /**
+ * The number of base units created.
+ *
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ private static long baseUnitCount = 0;
+
+ /**
+ * @return number of base units created
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ public static final long getBaseUnitCount() {
+ return baseUnitCount;
+ }
+
+ /**
+ * @return number of units created
+ * @since 2019-01-02
+ * @since v0.1.0
+ */
+ public static final long getUnitCount() {
+ return unitCount;
+ }
+
+ /**
+ * Increments the number of base units.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public static final void incrementBaseUnitCounter() {
+ baseUnitCount++;
+ }
+
+ /**
+ * Increments the number of units.
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public static final void incrementUnitCounter() {
+ unitCount++;
+ }
+
+ /**
+ * The dimension, or what the unit measures.
+ *
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ private final UnitDimension dimension;
+
+ /**
+ * The unit's base unit. Values converted by {@code convertFromBase} and {@code convertToBase} are expressed in this
+ * unit.
+ *
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ private final BaseUnit base;
+
+ /**
+ * The system that this unit is a part of.
+ *
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ private final UnitSystem system;
+
+ /**
+ * Creates the {@code AbstractUnit}.
+ *
+ * @param base
+ * unit's base
+ * @throws NullPointerException
+ * if name, symbol or base is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public AbstractUnit(final BaseUnit base) {
+ this.base = Objects.requireNonNull(base, "base must not be null.");
+ this.dimension = this.base.getDimension();
+ this.system = this.base.getSystem();
+ }
+
+ /**
+ * Creates the {@code AbstractUnit} using a unique dimension. This constructor is for making base units and should
+ * only be used by {@code BaseUnit}.
+ *
+ * @param dimension
+ * dimension measured by unit
+ * @param system
+ * system that unit is a part of
+ * @throws AssertionError
+ * if this constructor is not run by {@code BaseUnit} or a subclass
+ * @throws NullPointerException
+ * if name, symbol or dimension is null
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ AbstractUnit(final UnitDimension dimension, final UnitSystem system) {
+ // try to set this as a base unit
+ if (this instanceof BaseUnit) {
+ this.base = (BaseUnit) this;
+ } else
+ throw new AssertionError();
+
+ this.dimension = Objects.requireNonNull(dimension, "dimension must not be null.");
+ this.system = Objects.requireNonNull(system, "system must not be null.");
+ }
+
+ @Override
+ public final BaseUnit getBase() {
+ return this.base;
+ }
+
+ @Override
+ public final UnitDimension getDimension() {
+ return this.dimension;
+ }
+
+ @Override
+ public final UnitSystem getSystem() {
+ return this.system;
+ }
+
+ // TODO document and revise units' toString methods
+ @Override
+ public String toString() {
+ return String.format("%s-derived unit of dimension %s", this.getSystem(), this.getDimension());
+ }
+}
diff --git a/src/unitConverter/unit/BaseUnit.java b/src/unitConverter/unit/BaseUnit.java
new file mode 100755
index 0000000..fe36c45
--- /dev/null
+++ b/src/unitConverter/unit/BaseUnit.java
@@ -0,0 +1,180 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+import java.util.Objects;
+
+import unitConverter.dimension.UnitDimension;
+
+/**
+ * A unit that is the base for its dimension. It does not have to be for a base dimension, so units like the Newton and
+ * Joule are still base units.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+public final class BaseUnit extends AbstractUnit {
+ /**
+ * Is this unit a full base (i.e. m, s, ... but not N, J, ...)
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ private final boolean isFullBase;
+
+ /**
+ * Creates the {@code BaseUnit}.
+ *
+ * @param dimension
+ * dimension measured by unit
+ * @param system
+ * system that unit is a part of
+ * @param name
+ * name of unit
+ * @param symbol
+ * symbol of unit
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ BaseUnit(final UnitDimension dimension, final UnitSystem system) {
+ super(dimension, system);
+ this.isFullBase = dimension.isBase();
+ }
+
+ /**
+ * @return this unit as a {@code LinearUnit}
+ * @since 2019-01-25
+ * @since v0.1.0
+ */
+ public LinearUnit asLinearUnit() {
+ return this.times(1);
+ }
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value;
+ }
+
+ /**
+ * Divides this unit by another unit.
+ *
+ * @param other
+ * unit to divide by
+ * @return quotient of two units
+ * @throws IllegalArgumentException
+ * if this unit's system is not other's system
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public BaseUnit dividedBy(final BaseUnit other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ if (!this.getSystem().equals(other.getSystem()))
+ throw new IllegalArgumentException("Incompatible base units for division.");
+ return new BaseUnit(this.getDimension().dividedBy(other.getDimension()), this.getSystem());
+ }
+
+ /**
+ * Divides this unit by a divisor
+ *
+ * @param divisor
+ * amount to divide by
+ * @return quotient
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final double divisor) {
+ return new LinearUnit(this, 1 / divisor);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof BaseUnit))
+ return false;
+ final BaseUnit other = (BaseUnit) obj;
+ return Objects.equals(this.getSystem(), other.getSystem())
+ && Objects.equals(this.getDimension(), other.getDimension());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = result * prime + this.getSystem().hashCode();
+ result = result * prime + this.getDimension().hashCode();
+ return result;
+ }
+
+ /**
+ * Multiplies this unit by another unit.
+ *
+ * @param other
+ * unit to multiply by
+ * @return product of two units
+ * @throws IllegalArgumentException
+ * if this unit's system is not other's system
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public BaseUnit times(final BaseUnit other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ if (!this.getSystem().equals(other.getSystem()))
+ throw new IllegalArgumentException("Incompatible base units for multiplication.");
+ return new BaseUnit(this.getDimension().times(other.getDimension()), this.getSystem());
+ }
+
+ /**
+ * Multiplies this unit by a multiplier.
+ *
+ * @param multiplier
+ * amount to multiply by
+ * @return product
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit times(final double multiplier) {
+ return new LinearUnit(this, multiplier);
+ }
+
+ /**
+ * Returns this unit, but to an exponent.
+ *
+ * @param exponent
+ * exponent
+ * @return result of exponentiation
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public BaseUnit toExponent(final int exponent) {
+ return this.getSystem().getBaseUnit(this.getDimension().toExponent(exponent));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s base unit of%s dimension %s", this.getSystem(), this.isFullBase ? " base" : "",
+ this.getDimension());
+ }
+}
diff --git a/src/unitConverter/unit/DefaultUnitPrefix.java b/src/unitConverter/unit/DefaultUnitPrefix.java
new file mode 100755
index 0000000..d19161b
--- /dev/null
+++ b/src/unitConverter/unit/DefaultUnitPrefix.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+import java.util.Objects;
+
+/**
+ * The default implementation of {@code UnitPrefix}, which contains a multiplier and nothing else.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public final class DefaultUnitPrefix implements UnitPrefix {
+ private final double multiplier;
+
+ /**
+ * Creates the {@code DefaultUnitPrefix}.
+ *
+ * @param multiplier
+ * @since 2019-01-14
+ */
+ public DefaultUnitPrefix(final double multiplier) {
+ this.multiplier = multiplier;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof DefaultUnitPrefix))
+ return false;
+ final DefaultUnitPrefix other = (DefaultUnitPrefix) obj;
+ return Double.doubleToLongBits(this.multiplier) == Double.doubleToLongBits(other.multiplier);
+ }
+
+ @Override
+ public double getMultiplier() {
+ return this.multiplier;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.multiplier);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Unit prefix equal to %s", this.multiplier);
+ }
+}
diff --git a/src/unitConverter/unit/LinearUnit.java b/src/unitConverter/unit/LinearUnit.java
new file mode 100644
index 0000000..b786b3b
--- /dev/null
+++ b/src/unitConverter/unit/LinearUnit.java
@@ -0,0 +1,184 @@
+/**
+ * Copyright (C) 2019 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+import java.util.Objects;
+
+import unitConverter.dimension.UnitDimension;
+
+/**
+ * A unit that is equal to a certain number multiplied by its base.
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+public final class LinearUnit extends AbstractUnit {
+ /**
+ * The value of one of this unit in this unit's base unit
+ *
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ private final double conversionFactor;
+
+ /**
+ *
+ * Creates the {@code LinearUnit}.
+ *
+ * @param base
+ * unit's base
+ * @param conversionFactor
+ * value of one of this unit in its base
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ LinearUnit(final BaseUnit base, final double conversionFactor) {
+ super(base);
+ this.conversionFactor = conversionFactor;
+ }
+
+ /**
+ * Creates the {@code LinearUnit} as a base unit.
+ *
+ * @param dimension
+ * dimension measured by unit
+ * @param system
+ * system unit is part of
+ * @since 2019-01-25
+ * @since v0.1.0
+ */
+ LinearUnit(final UnitDimension dimension, final UnitSystem system, final double conversionFactor) {
+ super(dimension, system);
+ this.conversionFactor = conversionFactor;
+ }
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value / this.getConversionFactor();
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value * this.getConversionFactor();
+ }
+
+ /**
+ * Divides this unit by a scalar.
+ *
+ * @param divisor
+ * scalar to divide by
+ * @return quotient
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final double divisor) {
+ return new LinearUnit(this.getBase(), this.getConversionFactor() / divisor);
+ }
+
+ /**
+ * Divides this unit by another unit.
+ *
+ * @param other
+ * unit to divide by
+ * @return quotient of two units
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public LinearUnit dividedBy(final LinearUnit other) {
+ Objects.requireNonNull(other, "other must not be null");
+ final BaseUnit base = this.getBase().dividedBy(other.getBase());
+ return new LinearUnit(base, this.getConversionFactor() / other.getConversionFactor());
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof LinearUnit))
+ return false;
+ final LinearUnit other = (LinearUnit) obj;
+ return Objects.equals(this.getBase(), other.getBase())
+ && Objects.equals(this.getConversionFactor(), other.getConversionFactor());
+ }
+
+ /**
+ * @return conversionFactor
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public final double getConversionFactor() {
+ return this.conversionFactor;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = result * prime + this.getBase().hashCode();
+ result = result * prime + Double.hashCode(this.getConversionFactor());
+ return result;
+ }
+
+ /**
+ * Multiplies this unit by a scalar.
+ *
+ * @param multiplier
+ * scalar to multiply by
+ * @return product
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+ public LinearUnit times(final double multiplier) {
+ return new LinearUnit(this.getBase(), this.getConversionFactor() * multiplier);
+ }
+
+ /**
+ * Multiplies this unit by another unit.
+ *
+ * @param other
+ * unit to multiply by=
+ * @return product of two units
+ * @throws NullPointerException
+ * if other is null
+ * @since 2018-12-22
+ * @since v0.1.0
+ */
+ public LinearUnit times(final LinearUnit other) {
+ Objects.requireNonNull(other, "other must not be null");
+ final BaseUnit base = this.getBase().times(other.getBase());
+ return new LinearUnit(base, this.getConversionFactor() * other.getConversionFactor());
+ }
+
+ /**
+ * Returns this unit but to an exponent.
+ *
+ * @param exponent
+ * exponent to exponientate unit to
+ * @return exponientated unit
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public LinearUnit toExponent(final int exponent) {
+ return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent));
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + String.format(" (equal to %s * base)", this.getConversionFactor());
+ }
+}
diff --git a/src/unitConverter/unit/NonlinearUnits.java b/src/unitConverter/unit/NonlinearUnits.java
new file mode 100755
index 0000000..ec1874c
--- /dev/null
+++ b/src/unitConverter/unit/NonlinearUnits.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+/**
+ * Some major nonlinear units.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public final class NonlinearUnits {
+ public static final Unit CELSIUS = new AbstractUnit(SI.KELVIN) {
+
+ @Override
+ public double convertFromBase(final double value) {
+ return value - 273.15;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return value + 273.15;
+ }
+ };
+
+ public static final Unit FAHRENHEIT = new AbstractUnit(SI.KELVIN) {
+
+ @Override
+ public double convertFromBase(final double value) {
+ return 1.8 * value - 459.67;
+ }
+
+ @Override
+ public double convertToBase(final double value) {
+ return (value + 459.67) / 1.8;
+ }
+ };
+
+ // You may NOT get a NonlinearUnits instance.
+ private NonlinearUnits() {
+ throw new AssertionError();
+ }
+}
diff --git a/src/unitConverter/unit/SI.java b/src/unitConverter/unit/SI.java
new file mode 100644
index 0000000..54eb4c5
--- /dev/null
+++ b/src/unitConverter/unit/SI.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import unitConverter.dimension.StandardDimensions;
+import unitConverter.dimension.UnitDimension;
+
+/**
+ * The SI, which holds all SI units
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-23
+ * @since v0.1.0
+ */
+public enum SI implements UnitSystem {
+ SI;
+
+ /**
+ * This system's base units.
+ *
+ * @since 2019-01-25
+ * @since v0.1.0
+ */
+ private static final Set<BaseUnit> baseUnits = new HashSet<>();
+
+ // base units
+ public static final BaseUnit METRE = SI.getBaseUnit(StandardDimensions.LENGTH);
+ public static final BaseUnit KILOGRAM = SI.getBaseUnit(StandardDimensions.MASS);
+ public static final BaseUnit SECOND = SI.getBaseUnit(StandardDimensions.TIME);
+ public static final BaseUnit AMPERE = SI.getBaseUnit(StandardDimensions.ELECTRIC_CURRENT);
+ public static final BaseUnit KELVIN = SI.getBaseUnit(StandardDimensions.TEMPERATURE);
+ public static final BaseUnit MOLE = SI.getBaseUnit(StandardDimensions.QUANTITY);
+ public static final BaseUnit CANDELA = SI.getBaseUnit(StandardDimensions.LUMINOUS_INTENSITY);
+
+ @Override
+ public BaseUnit getBaseUnit(final UnitDimension dimension) {
+ // try to find an existing unit before creating a new one
+
+ Objects.requireNonNull(dimension, "dimension must not be null.");
+ for (final BaseUnit unit : baseUnits) {
+ // it will be equal since the conditions for equality are dimension and system,
+ // and system is always SI.
+ if (unit.getDimension().equals(dimension))
+ return unit;
+ }
+ // could not find an existing base unit
+ final BaseUnit unit = new BaseUnit(dimension, this);
+ baseUnits.add(unit);
+ return unit;
+ }
+
+ @Override
+ public String getName() {
+ return "SI";
+ }
+}
diff --git a/src/unitConverter/unit/SIPrefix.java b/src/unitConverter/unit/SIPrefix.java
new file mode 100755
index 0000000..54625fb
--- /dev/null
+++ b/src/unitConverter/unit/SIPrefix.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+/**
+ * The SI prefixes.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public enum SIPrefix implements UnitPrefix {
+ DECA(10), HECTO(100), KILO(1e3), MEGA(1e6), GIGA(1e9), TERA(1e12), PETA(1e15), EXA(1e18), ZETTA(1e21), YOTTA(
+ 1e24), DECI(0.1), CENTI(0.01), MILLI(
+ 1e-3), MICRO(1e-6), NANO(1e-9), PICO(1e-12), FEMTO(1e-15), ATTO(1e-18), ZEPTO(1e-21), YOCTO(1e-24);
+
+ private final double multiplier;
+
+ /**
+ * Creates the {@code SIPrefix}.
+ *
+ * @param multiplier
+ * prefix's multiplier
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ private SIPrefix(final double multiplier) {
+ this.multiplier = multiplier;
+ }
+
+ /**
+ * @return value
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ @Override
+ public final double getMultiplier() {
+ return this.multiplier;
+ }
+}
diff --git a/src/unitConverter/unit/Unit.java b/src/unitConverter/unit/Unit.java
index 3e7f9da..54f1423 100755
--- a/src/unitConverter/unit/Unit.java
+++ b/src/unitConverter/unit/Unit.java
@@ -25,6 +25,7 @@ import unitConverter.dimension.UnitDimension;
*
* @author Adrien Hopkins
* @since 2018-12-22
+ * @since v0.1.0
*/
public interface Unit {
/**
@@ -34,6 +35,7 @@ public interface Unit {
* unit to test with
* @return true if the units are compatible
* @since 2019-01-13
+ * @since v0.1.0
*/
default boolean canConvertTo(final Unit other) {
return Objects.equals(this.getBase(), other.getBase());
@@ -53,6 +55,7 @@ public interface Unit {
* value expressed in <b>base</b> unit
* @return value expressed in <b>this</b> unit
* @since 2018-12-22
+ * @since v0.1.0
*/
double convertFromBase(double value);
@@ -70,6 +73,7 @@ public interface Unit {
* value expressed in <b>this</b> unit
* @return value expressed in <b>base</b> unit
* @since 2018-12-22
+ * @since v0.1.0
*/
double convertToBase(double value);
@@ -86,18 +90,21 @@ public interface Unit {
*
* @return base unit associated with this unit
* @since 2018-12-22
+ * @since v0.1.0
*/
- Unit getBase();
+ BaseUnit getBase();
/**
* @return dimension measured by this unit
* @since 2018-12-22
+ * @since v0.1.0
*/
UnitDimension getDimension();
/**
* @return system that this unit is a part of
* @since 2018-12-23
+ * @since v0.1.0
*/
UnitSystem getSystem();
}
diff --git a/src/unitConverter/unit/UnitPrefix.java b/src/unitConverter/unit/UnitPrefix.java
new file mode 100755
index 0000000..a0c1b7c
--- /dev/null
+++ b/src/unitConverter/unit/UnitPrefix.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+/**
+ * A prefix that can be attached onto the front of any unit, which multiplies it by a certain value
+ *
+ * @author Adrien Hopkins
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+public interface UnitPrefix {
+ /**
+ * @return this prefix's multiplier
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ double getMultiplier();
+}
diff --git a/src/unitConverter/unit/UnitSystem.java b/src/unitConverter/unit/UnitSystem.java
index 2e3a5d8..ce8c249 100755
--- a/src/unitConverter/unit/UnitSystem.java
+++ b/src/unitConverter/unit/UnitSystem.java
@@ -16,16 +16,38 @@
*/
package unitConverter.unit;
+import java.util.Objects;
+
+import unitConverter.dimension.UnitDimension;
+
/**
* A system of units. Each unit should be aware of its system.
*
* @author Adrien Hopkins
* @since 2018-12-23
+ * @since v0.1.0
*/
public interface UnitSystem {
/**
- * @return name of system
+ * Gets a base unit for this system and the provided dimension.
+ *
+ * @param dimension
+ * dimension used by base unit
+ * @return base unit
+ * @throws NullPointerException
+ * if dimension is null
* @since 2019-01-25
+ * @since v0.1.0
+ */
+ default BaseUnit getBaseUnit(final UnitDimension dimension) {
+ Objects.requireNonNull(dimension, "dimension must not be null.");
+ return new BaseUnit(dimension, this);
+ }
+
+ /**
+ * @return name of system
+ * @since 2018-12-23
+ * @since v0.1.0
*/
String getName();
}
diff --git a/src/unitConverter/unit/UnitTest.java b/src/unitConverter/unit/UnitTest.java
new file mode 100755
index 0000000..c3237eb
--- /dev/null
+++ b/src/unitConverter/unit/UnitTest.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.unit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import unitConverter.dimension.StandardDimensions;
+
+/**
+ * Testing the various Unit classes
+ *
+ * @author Adrien Hopkins
+ * @since 2018-12-22
+ */
+class UnitTest {
+ @Test
+ void testConversion() {
+ final BaseUnit metre = SI.METRE;
+ final Unit inch = metre.times(0.0254);
+
+ assertEquals(1.9, inch.convertToBase(75), 0.01);
+ }
+
+ @Test
+ void testEquals() {
+ final BaseUnit metre = SI.METRE;
+ final Unit meter = SI.SI.getBaseUnit(StandardDimensions.LENGTH);
+
+ assertEquals(metre, meter);
+ }
+}