diff options
Diffstat (limited to 'src/unitConverter/UnitsDatabase.java')
-rwxr-xr-x | src/unitConverter/UnitsDatabase.java | 584 |
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()); - } -} |