Added unit registration to Units and Units.get methods
These are just convenience methods: instead of writing float g = 9.8f * Units.METERS_PER_SECOND_SQUARED; we can write float g = Units.get("9.8 m/s^2"); and it'll actually work fast.
This commit is contained in:
parent
4c3f5caae6
commit
3782cf1f88
@ -1,11 +1,34 @@
|
|||||||
package ru.windcorp.progressia.common;
|
package ru.windcorp.progressia.common;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
import gnu.trove.TCollections;
|
||||||
|
import gnu.trove.map.TCharFloatMap;
|
||||||
|
import gnu.trove.map.TObjectFloatMap;
|
||||||
|
import gnu.trove.map.hash.TCharFloatHashMap;
|
||||||
|
import gnu.trove.map.hash.TObjectFloatHashMap;
|
||||||
|
import ru.windcorp.jputil.chars.StringUtil;
|
||||||
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
|
||||||
public class Units {
|
public class Units {
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public static @interface RegisteredUnit {
|
||||||
|
String[] value();
|
||||||
|
}
|
||||||
|
|
||||||
// Base units
|
// Base units
|
||||||
// We're SI.
|
// We're SI.
|
||||||
|
@RegisteredUnit("m")
|
||||||
public static final float METERS = 1;
|
public static final float METERS = 1;
|
||||||
public static final float KILOGRAMS = 1;
|
public static final float KILOGRAMS = 1;
|
||||||
|
@RegisteredUnit("s")
|
||||||
public static final float SECONDS = 1;
|
public static final float SECONDS = 1;
|
||||||
|
|
||||||
// Length
|
// Length
|
||||||
@ -26,7 +49,9 @@ public class Units {
|
|||||||
public static final float CUBIC_KILOMETERS = KILOMETERS * KILOMETERS * KILOMETERS;
|
public static final float CUBIC_KILOMETERS = KILOMETERS * KILOMETERS * KILOMETERS;
|
||||||
|
|
||||||
// Mass
|
// Mass
|
||||||
|
@RegisteredUnit("g")
|
||||||
public static final float GRAMS = KILOGRAMS / 1000;
|
public static final float GRAMS = KILOGRAMS / 1000;
|
||||||
|
@RegisteredUnit("t")
|
||||||
public static final float TONNES = KILOGRAMS * 1000;
|
public static final float TONNES = KILOGRAMS * 1000;
|
||||||
|
|
||||||
// Density
|
// Density
|
||||||
@ -35,11 +60,15 @@ public class Units {
|
|||||||
|
|
||||||
// Time
|
// Time
|
||||||
public static final float MILLISECONDS = SECONDS / 1000;
|
public static final float MILLISECONDS = SECONDS / 1000;
|
||||||
|
@RegisteredUnit({"min", "mins", "minute", "minutes"})
|
||||||
public static final float MINUTES = SECONDS * 60;
|
public static final float MINUTES = SECONDS * 60;
|
||||||
|
@RegisteredUnit({"h", "hr", "hrs", "hour", "hours"})
|
||||||
public static final float HOURS = MINUTES * 60;
|
public static final float HOURS = MINUTES * 60;
|
||||||
|
@RegisteredUnit({"d", "day", "days"})
|
||||||
public static final float DAYS = HOURS * 24;
|
public static final float DAYS = HOURS * 24;
|
||||||
|
|
||||||
// Frequency
|
// Frequency
|
||||||
|
@RegisteredUnit("Hz")
|
||||||
public static final float HERTZ = 1 / SECONDS;
|
public static final float HERTZ = 1 / SECONDS;
|
||||||
public static final float KILOHERTZ = HERTZ * 1000;
|
public static final float KILOHERTZ = HERTZ * 1000;
|
||||||
|
|
||||||
@ -51,6 +80,222 @@ public class Units {
|
|||||||
public static final float METERS_PER_SECOND_SQUARED = METERS_PER_SECOND / SECONDS;
|
public static final float METERS_PER_SECOND_SQUARED = METERS_PER_SECOND / SECONDS;
|
||||||
|
|
||||||
// Force
|
// Force
|
||||||
|
@RegisteredUnit("N")
|
||||||
public static final float NEWTONS = METERS_PER_SECOND_SQUARED * KILOGRAMS;
|
public static final float NEWTONS = METERS_PER_SECOND_SQUARED * KILOGRAMS;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
registerUnits(Units.class);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
CrashReports.report(e, "Could not register units declared in {}", Units.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final TObjectFloatMap<String> UNITS_BY_NAME = createMap();
|
||||||
|
|
||||||
|
private static final TCharFloatMap PREFIXES_BY_CHAR;
|
||||||
|
static {
|
||||||
|
TCharFloatMap prefixes = new TCharFloatHashMap(
|
||||||
|
gnu.trove.impl.Constants.DEFAULT_CAPACITY,
|
||||||
|
gnu.trove.impl.Constants.DEFAULT_LOAD_FACTOR,
|
||||||
|
gnu.trove.impl.Constants.DEFAULT_CHAR_NO_ENTRY_VALUE,
|
||||||
|
Float.NaN
|
||||||
|
);
|
||||||
|
|
||||||
|
prefixes.put('G', 1e+9f);
|
||||||
|
prefixes.put('M', 1e+6f);
|
||||||
|
prefixes.put('k', 1e+3f);
|
||||||
|
prefixes.put('c', 1e-2f);
|
||||||
|
prefixes.put('m', 1e-3f);
|
||||||
|
prefixes.put('μ', 1e-6f);
|
||||||
|
|
||||||
|
PREFIXES_BY_CHAR = TCollections.unmodifiableMap(prefixes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final TObjectFloatMap<String> KNOWN_UNITS = createMap();
|
||||||
|
|
||||||
|
private static TObjectFloatMap<String> createMap() {
|
||||||
|
return TCollections.synchronizedMap(
|
||||||
|
new TObjectFloatHashMap<>(
|
||||||
|
gnu.trove.impl.Constants.DEFAULT_CAPACITY,
|
||||||
|
gnu.trove.impl.Constants.DEFAULT_LOAD_FACTOR,
|
||||||
|
Float.NaN
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerUnits(Class<?> source) throws IllegalAccessException {
|
||||||
|
for (Field field : source.getDeclaredFields()) {
|
||||||
|
int mods = field.getModifiers();
|
||||||
|
|
||||||
|
if (!Modifier.isPublic(mods)) continue;
|
||||||
|
if (!Modifier.isStatic(mods)) continue;
|
||||||
|
if (!Modifier.isFinal(mods)) continue;
|
||||||
|
if (field.getType() != Float.TYPE) continue;
|
||||||
|
|
||||||
|
RegisteredUnit request = field.getAnnotation(RegisteredUnit.class);
|
||||||
|
float value = field.getFloat(null); // adding throws since we might not have accounted for something
|
||||||
|
registerUnit(value, request.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerUnit(float value, String... names) {
|
||||||
|
for (String name : names) {
|
||||||
|
if (!Float.isNaN(UNITS_BY_NAME.put(name, value))) {
|
||||||
|
throw new IllegalArgumentException("Duplicate unit name " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the unit described by {@code declar}.
|
||||||
|
*
|
||||||
|
* <p>The general form of a declaration is:
|
||||||
|
* <pre>
|
||||||
|
* unit_declar ::= [ws]unit_declar_part[[ws]"/"[ws]unit_declar_part][ws]
|
||||||
|
* unit_declar_part ::= unit_name_and_exp[[ws]"*"[ws]unit_name_and_exp]+
|
||||||
|
* unit_name_and_exp ::= unit_name[[ws]"^"[ws]exponent]
|
||||||
|
* unit_name ::= [prefix]named_unit | special_unit
|
||||||
|
* named_unit ::= <any registered unit name, case-sensitive>
|
||||||
|
* prefix ::= "G" | "M" | "k" | "c" | "m" | "μ"
|
||||||
|
* special_unit ::= "1"
|
||||||
|
* exponent ::= <any float>
|
||||||
|
* ws ::= <any character <= 'U+0020'>+</pre>
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* <ul>
|
||||||
|
* <li>seconds = {@code "s"}</li>
|
||||||
|
* <li>meters per second = {@code "m/s"}</li>
|
||||||
|
* <li>kilonewtons = {@code "kN"}</li>
|
||||||
|
* <li>square meters = {@code "m^2"}</li>
|
||||||
|
* <li>units per meter = {@code "1/m"}</li>
|
||||||
|
* <li>units of gravitational constant G = {@code "m^3/kg*s^2"} [sic] (see below)</li>
|
||||||
|
* <li>units (dimensionless) = {@code "1"}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* Note that no more than one {@code '/'} is allowed per declaration, and no parenthesis are allowed at all. As such,
|
||||||
|
* <ul>
|
||||||
|
* <li>Multiple units under the division bar should be located after the single {@code '/'} and separated by {@code '*'}:
|
||||||
|
* <a href=https://en.wikipedia.org/wiki/Gas_constant>gas constant</a> ought to have {@code "J/K*mol"} units.</li>
|
||||||
|
* <li>Exponentiation of parenthesis should be expanded: (m/s)² = {@code "m^2/s^2"}.</li>
|
||||||
|
* <li>Exponents should also be used for expressing roots: √s = {@code "s^0.5"}.</li>
|
||||||
|
* <li>Exponents can be used to express division, but such use is generally discouraged.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param unit unit declaration
|
||||||
|
* @throws IllegalArgumentException if the declaration is invalid
|
||||||
|
* @return the value of the unit
|
||||||
|
*
|
||||||
|
* @see #get(String) get(String)
|
||||||
|
* @see #registerUnit(float, String...)
|
||||||
|
*/
|
||||||
|
public static final float getUnitValue(String unit) {
|
||||||
|
float cached = KNOWN_UNITS.get(unit);
|
||||||
|
if (!Float.isNaN(cached)) return cached;
|
||||||
|
|
||||||
|
float computed = computeUnitValue(unit);
|
||||||
|
KNOWN_UNITS.put(unit, computed);
|
||||||
|
return computed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float computeUnitValue(String unit) {
|
||||||
|
String[] parts = StringUtil.split(unit, '/');
|
||||||
|
|
||||||
|
assert parts != null && parts.length != 0;
|
||||||
|
|
||||||
|
switch (parts.length) {
|
||||||
|
case 1:
|
||||||
|
return parseUnitValue(parts[0]);
|
||||||
|
case 2:
|
||||||
|
return parseUnitValue(parts[0]) / parseUnitValue(parts[1]);
|
||||||
|
default:
|
||||||
|
throw invalidUnit(unit, "unit declaration contains more than one '/'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float parseUnitValue(String declar) {
|
||||||
|
String[] unitsAndExponents = StringUtil.split(declar, '*');
|
||||||
|
|
||||||
|
float result = 1;
|
||||||
|
for (String unitAndExponent : unitsAndExponents) {
|
||||||
|
String[] parts = StringUtil.split(unitAndExponent, '^');
|
||||||
|
|
||||||
|
float exponent;
|
||||||
|
|
||||||
|
assert parts != null && parts.length != 0;
|
||||||
|
switch (parts.length) {
|
||||||
|
case 1:
|
||||||
|
exponent = 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
exponent = Float.parseFloat(parts[1].trim());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw invalidUnit(unitAndExponent, "unit declaration contains more than one '^'");
|
||||||
|
}
|
||||||
|
|
||||||
|
String unitName = parts[0].trim();
|
||||||
|
|
||||||
|
float value = parseUnitAsNamed(unitName);
|
||||||
|
if (Float.isNaN(value)) value = parseUnitAsNamedAndPrefixed(unitName);
|
||||||
|
if (Float.isNaN(value)) value = parseUnitAsSpecial(unitName);
|
||||||
|
if (Float.isNaN(value)) throw invalidUnit(unitName, "unknown unit name or unknown prefix or unknown special unit");
|
||||||
|
|
||||||
|
if (exponent != 1) {
|
||||||
|
value = (float) Math.pow(value, exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
result *= value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float parseUnitAsNamed(String namedUnit) {
|
||||||
|
return UNITS_BY_NAME.get(namedUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float parseUnitAsNamedAndPrefixed(String namedUnit) {
|
||||||
|
if (namedUnit.length() < 2) return Float.NaN;
|
||||||
|
|
||||||
|
float value = PREFIXES_BY_CHAR.get(namedUnit.charAt(0));
|
||||||
|
if (!Float.isNaN(value)) value *= parseUnitAsNamed(namedUnit.substring(1));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float parseUnitAsSpecial(String namedUnit) {
|
||||||
|
return namedUnit.equals("1") ? 1 : Float.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RuntimeException invalidUnit(String unit, String details) {
|
||||||
|
return new IllegalArgumentException("Invalid unit declaration \"" + unit + "\": " + details);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double get(double amount, String unit) {
|
||||||
|
return amount * getUnitValue(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float get(float amount, String unit) {
|
||||||
|
return amount * getUnitValue(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float get(String declar) {
|
||||||
|
String[] parts = StringUtil.split(declar, ' ', 2);
|
||||||
|
assert parts != null && parts.length != 0;
|
||||||
|
if (parts[1] == null) throw new IllegalArgumentException("No space (' ') found");
|
||||||
|
assert parts[0] == parts[0].trim();
|
||||||
|
return Float.parseFloat(parts[0]) * getUnitValue(parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getd(String declar) {
|
||||||
|
String[] parts = StringUtil.split(declar, ' ', 2);
|
||||||
|
assert parts != null && parts.length != 0;
|
||||||
|
if (parts[1] == null) throw new IllegalArgumentException("No space (' ') found");
|
||||||
|
assert parts[0] == parts[0].trim();
|
||||||
|
return Double.parseDouble(parts[0]) * getUnitValue(parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user