summaryrefslogtreecommitdiff
path: root/src/unitConverter/UnitsDatabase.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/unitConverter/UnitsDatabase.java')
-rwxr-xr-xsrc/unitConverter/UnitsDatabase.java584
1 files changed, 0 insertions, 584 deletions
diff --git a/src/unitConverter/UnitsDatabase.java b/src/unitConverter/UnitsDatabase.java
deleted file mode 100755
index 4816db1..0000000
--- a/src/unitConverter/UnitsDatabase.java
+++ /dev/null
@@ -1,584 +0,0 @@
-/**
- * Copyright (C) 2018 Adrien Hopkins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <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());
- }
-}