parts = Arrays.asList(name.split("\\("));
if (parts.size() != 2)
throw new IllegalArgumentException("Format nonlinear units like: unit(value).");
// solve the function
final Unit unit = this.getUnit(parts.get(0));
final double value = Double.parseDouble(parts.get(1).substring(0, parts.get(1).length() - 1));
return unit.getBase().times(unit.convertToBase(value));
} else {
// get a linear unit
final Unit unit = this.getUnit(name);
if (unit instanceof LinearUnit)
return (LinearUnit) unit;
else
throw new IllegalArgumentException(String.format("%s is not a linear unit.", name));
}
}
/**
* Gets a unit prefix from the database from its name
*
* @param name
* prefix's name
* @return prefix
* @since 2019-01-10
* @since v0.1.0
*/
public UnitPrefix getPrefix(final String name) {
try {
return new DefaultUnitPrefix(Double.parseDouble(name));
} catch (final NumberFormatException e) {
return this.prefixes.get(name);
}
}
/**
* Gets a unit prefix from a prefix expression
*
* Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of
* another prefix
*
*
* @param expression
* expression to input
* @return prefix
* @throws IllegalArgumentException
* if expression cannot be parsed
* @throws NullPointerException
* if any argument is null
* @since 2019-01-14
* @since v0.1.0
*/
public UnitPrefix getPrefixFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
// attempt to get a unit as an alias first
if (this.containsUnitName(expression))
return this.getPrefix(expression);
// force operators to have spaces
String modifiedExpression = expression;
modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
modifiedExpression = modifiedExpression.replaceAll("/", " / ");
modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
// fix broken spaces
modifiedExpression = modifiedExpression.replaceAll(" +", " ");
return this.prefixExpressionParser.parseExpression(modifiedExpression);
}
/**
* Gets a unit from the database from its name, ignoring prefixes.
*
* @param name
* unit's name
* @return unit
* @since 2019-01-10
* @since v0.1.0
*/
public Unit getPrefixlessUnit(final String name) {
return this.units.get(name);
}
/**
* Gets a unit from the database from its name, looking for prefixes.
*
* @param name
* unit's name
* @return unit
* @since 2019-01-10
* @since v0.1.0
*/
public Unit getUnit(final String name) {
try {
final double value = Double.parseDouble(name);
return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(value);
} catch (final NumberFormatException e) {
for (final String prefixName : this.prefixNameSet()) {
// check for a prefix
if (name.startsWith(prefixName)) {
// prefix found! Make sure what comes after it is actually a unit!
final String prefixless = name.substring(prefixName.length());
if (this.containsUnitName(prefixless)) {
// yep, it's a proper prefix! Get the unit!
final Unit unit = this.getUnit(prefixless);
final UnitPrefix prefix = this.getPrefix(prefixName);
// Prefixes only work with linear and base units, so make sure it's one of those
if (unit instanceof LinearUnit) {
final LinearUnit linearUnit = (LinearUnit) unit;
return linearUnit.withPrefix(prefix);
}
}
}
}
return this.units.get(name);
}
}
/**
* Uses the database's unit data to parse an expression into a unit
*
* The expression is a series of any of the following:
*
* - The name of a unit, which multiplies or divides the result based on preceding operators
* - The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each
* other is equivalent to multiplication)
* - The operator '^' which exponentiates. Exponents must be integers.
* - A number which is multiplied or divided
*
* This method only works with linear units.
*
* @param expression
* expression to parse
* @throws IllegalArgumentException
* if the expression cannot be parsed
* @throws NullPointerException
* if expression is null
* @since 2019-01-07
* @since v0.1.0
*/
public Unit getUnitFromExpression(final String expression) {
Objects.requireNonNull(expression, "expression must not be null.");
// attempt to get a unit as an alias first
if (this.containsUnitName(expression))
return this.getUnit(expression);
// force operators to have spaces
String modifiedExpression = expression;
modifiedExpression = modifiedExpression.replaceAll("\\+", " \\+ ");
modifiedExpression = modifiedExpression.replaceAll("-", " - ");
modifiedExpression = modifiedExpression.replaceAll("\\*", " \\* ");
modifiedExpression = modifiedExpression.replaceAll("/", " / ");
modifiedExpression = modifiedExpression.replaceAll("\\^", " \\^ ");
// fix broken spaces
modifiedExpression = modifiedExpression.replaceAll(" +", " ");
// the previous operation breaks negative numbers, fix them!
// (i.e. -2 becomes - 2)
for (int i = 2; i < modifiedExpression.length(); i++) {
if (modifiedExpression.charAt(i) == '-'
&& Arrays.asList('+', '-', '*', '/', '^').contains(modifiedExpression.charAt(i - 2))) {
// found a broken negative number
modifiedExpression = modifiedExpression.substring(0, i + 1) + modifiedExpression.substring(i + 2);
}
}
return this.unitExpressionParser.parseExpression(modifiedExpression);
}
/**
* Adds all dimensions from a file, using data from the database to parse them.
*
* Each line in the file should consist of a name and an expression (parsed by getDimensionFromExpression) separated
* by any number of tab characters.
*
*
* Allowed exceptions:
*
* - Anything after a '#' character is considered a comment and ignored.
* - Blank lines are also ignored
* - If an expression consists of a single exclamation point, instead of parsing it, this method will search the
* database for an existing unit. If no unit is found, an IllegalArgumentException is thrown. This is used to define
* initial units and ensure that the database contains them.
*
*
* @param file
* file to read
* @throws IllegalArgumentException
* if the file cannot be parsed, found or read
* @throws NullPointerException
* if file is null
* @since 2019-01-13
* @since v0.1.0
*/
public void loadDimensionFile(final File file) {
Objects.requireNonNull(file, "file must not be null.");
try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
// while the reader has lines to read, read a line, then parse it, then add it
long lineCounter = 0;
while (reader.ready()) {
this.addDimensionFromLine(reader.readLine(), ++lineCounter);
}
} catch (final FileNotFoundException e) {
throw new IllegalArgumentException("Could not find file " + file, e);
} catch (final IOException e) {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
/**
* Adds all units from a file, using data from the database to parse them.
*
* Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by
* any number of tab characters.
*
*
* Allowed exceptions:
*
* - Anything after a '#' character is considered a comment and ignored.
* - Blank lines are also ignored
* - If an expression consists of a single exclamation point, instead of parsing it, this method will search the
* database for an existing unit. If no unit is found, an IllegalArgumentException is thrown. This is used to define
* initial units and ensure that the database contains them.
*
*
* @param file
* file to read
* @throws IllegalArgumentException
* if the file cannot be parsed, found or read
* @throws NullPointerException
* if file is null
* @since 2019-01-13
* @since v0.1.0
*/
public void loadUnitsFile(final File file) {
Objects.requireNonNull(file, "file must not be null.");
try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) {
// while the reader has lines to read, read a line, then parse it, then add it
long lineCounter = 0;
while (reader.ready()) {
this.addUnitOrPrefixFromLine(reader.readLine(), ++lineCounter);
}
} catch (final FileNotFoundException e) {
throw new IllegalArgumentException("Could not find file " + file, e);
} catch (final IOException e) {
throw new IllegalArgumentException("Could not read file " + file, e);
}
}
/**
* @return an immutable set of all of the unit names in this database, ignoring prefixes
* @since 2019-01-14
* @since v0.1.0
*/
public Set prefixlessUnitNameSet() {
return Collections.unmodifiableSet(this.units.keySet());
}
/**
* @return an immutable set of all of the units in this database, ignoring prefixes.
* @since 2019-04-10
*/
public Set prefixlessUnitSet() {
return Collections.unmodifiableSet(new HashSet<>(this.units.values()));
}
/**
* @return an immutable set of all of the prefix names in this database
* @since 2019-01-14
* @since v0.1.0
*/
public Set prefixNameSet() {
return Collections.unmodifiableSet(this.prefixes.keySet());
}
}