[wpiunits] Change units to track their base unit, instead of their base class (#6342)

Unit objects now have a reference to the base unit from which they're derived. Constructing a unit object without specifying a base unit implicitly signifies that it's its own base unit, eg new Angle(null, 1, "Radian", "rad") would be the base angle unit of radians, while new Angle(Radians, 2 * PI, "Rotation", "R") would be a new angle unit based on radians.

This fixes much of the hacky code surrounding the derived unit types Velocity, Per, and Mult, but is a breaking change for any user code that defines custom unit classes or uses the anonymous unit type.
This commit is contained in:
Sam Carlberg
2024-05-12 09:15:56 -04:00
committed by GitHub
parent dc00a13d83
commit 6c9dcc157e
23 changed files with 312 additions and 162 deletions

View File

@@ -17,19 +17,16 @@ package edu.wpi.first.units;
// eg Mass * Distance * Velocity<Angle> is equivalent to (Mass * Distance) / Time - otherwise known
// as Power - in other words, Velocity<Angle> is /actually/ Frequency
public class Angle extends Unit<Angle> {
/**
* Creates a new unit with the given name and multiplier to the base unit.
*
* @param baseUnitEquivalent the multiplier to convert this unit to the base unit of this type
* @param name the name of the angle measure
* @param symbol the symbol of the angle measure
*/
Angle(double baseUnitEquivalent, String name, String symbol) {
super(Angle.class, baseUnitEquivalent, name, symbol);
Angle(Angle baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Angle(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Angle.class, toBaseConverter, fromBaseConverter, name, symbol);
Angle baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
}

View File

@@ -11,36 +11,33 @@ public final class BaseUnits {
}
/** The standard unit of distance, meters. */
public static final Distance Distance = new Distance(1, "Meter", "m");
public static final Distance Distance = new Distance(null, 1, "Meter", "m");
/** The standard unit of time, seconds. */
public static final Time Time = new Time(1, "Second", "s");
/** The standard unit of velocity, meters per second. */
public static final Velocity<Distance> Velocity =
new Velocity<>(Distance, Time, "Meter per Second", "m/s");
public static final Time Time = new Time(null, 1, "Second", "s");
/** The standard unit of mass, kilograms. */
public static final Mass Mass = new Mass(1, "Kilogram", "Kg");
public static final Mass Mass = new Mass(null, 1, "Kilogram", "Kg");
/** The standard unit of angles, radians. */
public static final Angle Angle = new Angle(1, "Radian", "rad");
public static final Angle Angle = new Angle(null, 1, "Radian", "rad");
/** The standard "unitless" unit. */
public static final Dimensionless Value = new Dimensionless(1, "<?>", "<?>");
public static final Dimensionless Value = new Dimensionless(null, 1, "<?>", "<?>");
/** The standard unit of voltage, volts. */
public static final Voltage Voltage = new Voltage(1, "Volt", "V");
public static final Voltage Voltage = new Voltage(null, 1, "Volt", "V");
/** The standard unit of electric current, amperes. */
public static final Current Current = new Current(1, "Amp", "A");
public static final Current Current = new Current(null, 1, "Amp", "A");
/** The standard unit of energy, joules. */
public static final Energy Energy = new Energy(1, "Joule", "J");
public static final Energy Energy = new Energy(null, 1, "Joule", "J");
/** The standard unit of power, watts. */
public static final Power Power = new Power(1, "Watt", "W");
public static final Power Power = new Power(null, 1, "Watt", "W");
/** The standard unit of temperature, kelvin. */
public static final Temperature Temperature = new Temperature(x -> x, x -> x, "Kelvin", "K");
public static final Temperature Temperature =
new Temperature(null, x -> x, x -> x, "Kelvin", "K");
}

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.units;
import static edu.wpi.first.units.Units.Watts;
/**
* Unit of electic current dimension.
*
@@ -14,13 +16,17 @@ package edu.wpi.first.units;
* {@link Units} class.
*/
public class Current extends Unit<Current> {
Current(double baseUnitEquivalent, String name, String symbol) {
super(Current.class, baseUnitEquivalent, name, symbol);
Current(Current baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Current(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Current.class, toBaseConverter, fromBaseConverter, name, symbol);
Current baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
/**
@@ -35,6 +41,6 @@ public class Current extends Unit<Current> {
* @return the power unit
*/
public Power times(Unit<Voltage> voltage, String name, String symbol) {
return new Power(this.toBaseUnits(1) * voltage.toBaseUnits(1), name, symbol);
return new Power(Watts, this.toBaseUnits(1) * voltage.toBaseUnits(1), name, symbol);
}
}

View File

@@ -15,12 +15,16 @@ public class Dimensionless extends Unit<Dimensionless> {
* @param name the name of the unit
* @param symbol the symbol of the unit
*/
Dimensionless(double baseUnitEquivalent, String name, String symbol) {
super(Dimensionless.class, baseUnitEquivalent, name, symbol);
Dimensionless(Dimensionless baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Dimensionless(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Dimensionless.class, toBaseConverter, fromBaseConverter, name, symbol);
Dimensionless baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
}

View File

@@ -14,13 +14,16 @@ package edu.wpi.first.units;
* {@link Units} class.
*/
public class Distance extends Unit<Distance> {
/** Creates a new unit with the given name and multiplier to the base unit. */
Distance(double baseUnitEquivalent, String name, String symbol) {
super(Distance.class, baseUnitEquivalent, name, symbol);
Distance(Distance baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Distance(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Distance.class, toBaseConverter, fromBaseConverter, name, symbol);
Distance baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
}

View File

@@ -15,11 +15,15 @@ package edu.wpi.first.units;
*/
public class Energy extends Unit<Energy> {
Energy(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Energy.class, toBaseConverter, fromBaseConverter, name, symbol);
Energy baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
Energy(double baseUnitEquivalent, String name, String symbol) {
super(Energy.class, baseUnitEquivalent, name, symbol);
Energy(Energy baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
}

View File

@@ -15,11 +15,16 @@ package edu.wpi.first.units;
*/
public class Mass extends Unit<Mass> {
/** Creates a new unit with the given name and multiplier to the base unit. */
Mass(double baseUnitEquivalent, String name, String symbol) {
super(Mass.class, baseUnitEquivalent, name, symbol);
Mass(Mass baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Mass(UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Mass.class, toBaseConverter, fromBaseConverter, name, symbol);
Mass(
Mass baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
}

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.units;
import static edu.wpi.first.units.Units.Seconds;
/**
* A measure holds the magnitude and unit of some dimension, such as distance, time, or speed. Two
* measures with the same <i>unit</i> and <i>magnitude</i> are effectively equivalent objects.
@@ -91,28 +93,28 @@ public interface Measure<U extends Unit<U>> extends Comparable<Measure<U>> {
}
if (unit() instanceof Per
&& other.unit().m_baseType.equals(((Per<?, ?>) unit()).denominator().m_baseType)) {
&& other.unit().getBaseUnit().equals(((Per<?, ?>) unit()).denominator().getBaseUnit())) {
// denominator of the Per cancels out, return with just the units of the numerator
Unit<?> numerator = ((Per<?, ?>) unit()).numerator();
return numerator.ofBaseUnits(baseUnitMagnitude() * other.baseUnitMagnitude());
} else if (unit() instanceof Velocity && other.unit().m_baseType.equals(Time.class)) {
} else if (unit() instanceof Velocity && other.unit().getBaseUnit().equals(Seconds)) {
// Multiplying a velocity by a time, return the scalar unit (eg Distance)
Unit<?> numerator = ((Velocity<?>) unit()).getUnit();
return numerator.ofBaseUnits(baseUnitMagnitude() * other.baseUnitMagnitude());
} else if (other.unit() instanceof Per
&& unit().m_baseType.equals(((Per<?, ?>) other.unit()).denominator().m_baseType)) {
&& unit().getBaseUnit().equals(((Per<?, ?>) other.unit()).denominator().getBaseUnit())) {
Unit<?> numerator = ((Per<?, ?>) other.unit()).numerator();
return numerator.ofBaseUnits(baseUnitMagnitude() * other.baseUnitMagnitude());
} else if (unit() instanceof Per
&& other.unit() instanceof Per
&& ((Per<?, ?>) unit())
.denominator()
.m_baseType
.equals(((Per<?, U>) other.unit()).numerator().m_baseType)
.getBaseUnit()
.equals(((Per<?, U>) other.unit()).numerator().getBaseUnit())
&& ((Per<?, ?>) unit())
.numerator()
.m_baseType
.equals(((Per<?, ?>) other.unit()).denominator().m_baseType)) {
.getBaseUnit()
.equals(((Per<?, ?>) other.unit()).denominator().getBaseUnit())) {
// multiplying eg meters per second * milliseconds per foot
// return a scalar
return Units.Value.of(baseUnitMagnitude() * other.baseUnitMagnitude());
@@ -255,7 +257,7 @@ public interface Measure<U extends Unit<U>> extends Comparable<Measure<U>> {
* @return true if this unit is near the other measure, otherwise false
*/
default boolean isNear(Measure<?> other, double varianceThreshold) {
if (this.unit().m_baseType != other.unit().m_baseType) {
if (!this.unit().getBaseUnit().equivalent(other.unit().getBaseUnit())) {
return false; // Disjoint units, not compatible
}
@@ -291,7 +293,7 @@ public interface Measure<U extends Unit<U>> extends Comparable<Measure<U>> {
* @return true if this measure is equivalent, false otherwise
*/
default boolean isEquivalent(Measure<?> other) {
if (this.unit().m_baseType != other.unit().m_baseType) {
if (!this.unit().getBaseUnit().equals(other.unit().getBaseUnit())) {
return false; // Disjoint units, not compatible
}

View File

@@ -26,13 +26,12 @@ public class Mult<A extends Unit<A>, B extends Unit<B>> extends Unit<Mult<A, B>>
* Creates a new product unit. Consider using {@link #combine} instead of manually calling this
* constructor.
*
* @param baseType the base type representing the unit product
* @param a the first unit of the product
* @param b the second unit of the product
*/
protected Mult(Class<? extends Mult<A, B>> baseType, A a, B b) {
protected Mult(A a, B b) {
super(
baseType,
a.isBaseUnit() && b.isBaseUnit() ? null : combine(a.getBaseUnit(), b.getBaseUnit()),
a.toBaseUnits(1) * b.toBaseUnits(1),
a.name() + "-" + b.name(),
a.symbol() + "*" + b.symbol());
@@ -40,6 +39,17 @@ public class Mult<A extends Unit<A>, B extends Unit<B>> extends Unit<Mult<A, B>>
m_unitB = b;
}
Mult(
Mult<A, B> baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
m_unitA = baseUnit.unitA();
m_unitB = baseUnit.unitB();
}
/**
* Creates a new Mult unit derived from two arbitrary units multiplied together.
*
@@ -63,7 +73,7 @@ public class Mult<A extends Unit<A>, B extends Unit<B>> extends Unit<Mult<A, B>>
return cache.get(key);
}
var mult = new Mult<A, B>((Class) Mult.class, a, b);
var mult = new Mult<A, B>(a, b);
cache.put(key, mult);
return mult;
}

View File

@@ -32,13 +32,14 @@ public class Per<N extends Unit<N>, D extends Unit<D>> extends Unit<Per<N, D>> {
* Creates a new proportional unit derived from the ratio of one unit to another. Consider using
* {@link #combine} instead of manually calling this constructor.
*
* @param baseType the base type representing the unit ratio
* @param numerator the numerator unit
* @param denominator the denominator unit
*/
protected Per(Class<Per<N, D>> baseType, N numerator, D denominator) {
protected Per(N numerator, D denominator) {
super(
baseType,
numerator.isBaseUnit() && denominator.isBaseUnit()
? null
: combine(numerator.getBaseUnit(), denominator.getBaseUnit()),
numerator.toBaseUnits(1) / denominator.toBaseUnits(1),
numerator.name() + " per " + denominator.name(),
numerator.symbol() + "/" + denominator.symbol());
@@ -46,6 +47,17 @@ public class Per<N extends Unit<N>, D extends Unit<D>> extends Unit<Per<N, D>> {
m_denominator = denominator;
}
Per(
Per<N, D> baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
m_numerator = baseUnit.numerator();
m_denominator = baseUnit.denominator();
}
/**
* Creates a new Per unit derived from an arbitrary numerator and time denominator units. Using a
* denominator with a unit of time is discouraged; use {@link Velocity} instead.
@@ -63,7 +75,7 @@ public class Per<N extends Unit<N>, D extends Unit<D>> extends Unit<Per<N, D>> {
* @param denominator the denominator for unit time
* @return the combined unit
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings("unchecked")
public static <N extends Unit<N>, D extends Unit<D>> Per<N, D> combine(
N numerator, D denominator) {
final long key =
@@ -74,7 +86,7 @@ public class Per<N extends Unit<N>, D extends Unit<D>> extends Unit<Per<N, D>> {
return existing;
}
var newUnit = new Per<N, D>((Class) Per.class, numerator, denominator);
var newUnit = new Per<>(numerator, denominator);
cache.put(key, newUnit);
return newUnit;
}

View File

@@ -14,12 +14,16 @@ package edu.wpi.first.units;
* {@link Units} class.
*/
public class Power extends Unit<Power> {
Power(double baseUnitEquivalent, String name, String symbol) {
super(Power.class, baseUnitEquivalent, name, symbol);
Power(Power baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Power(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Power.class, toBaseConverter, fromBaseConverter, name, symbol);
Power baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
}

View File

@@ -15,7 +15,11 @@ package edu.wpi.first.units;
*/
public class Temperature extends Unit<Temperature> {
Temperature(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Temperature.class, toBaseConverter, fromBaseConverter, name, symbol);
Temperature baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
}

View File

@@ -14,12 +14,16 @@ package edu.wpi.first.units;
* the {@link Units} class.
*/
public class Time extends Unit<Time> {
/** Creates a new unit with the given name and multiplier to the base unit. */
Time(double baseUnitEquivalent, String name, String symbol) {
super(Time.class, baseUnitEquivalent, name, symbol);
Time(Time baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Time(UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Time.class, toBaseConverter, fromBaseConverter, name, symbol);
Time(
Time baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
}

View File

@@ -15,6 +15,9 @@ import java.util.Objects;
*/
@FunctionalInterface
public interface UnaryFunction {
/** The identity function that simply returns the input value. */
UnaryFunction IDENTITY = x -> x;
/**
* Applies this function to the input value and returns the result.
*

View File

@@ -18,7 +18,7 @@ public class Unit<U extends Unit<U>> {
private final UnaryFunction m_toBaseConverter;
private final UnaryFunction m_fromBaseConverter;
final Class<? extends U> m_baseType; // package-private for the builder
private final U m_baseUnit;
private Measure<U> m_zero;
private Measure<U> m_one;
@@ -29,19 +29,21 @@ public class Unit<U extends Unit<U>> {
/**
* Creates a new unit defined by its relationship to some base unit.
*
* @param baseType the base type of the unit, e.g. Distance.class for the distance unit
* @param baseUnit the base unit, e.g. Meters for distances. Set this to {@code null} if the unit
* being constructed is its own base unit
* @param toBaseConverter a function for converting units of this type to the base unit
* @param fromBaseConverter a function for converting units of the base unit to this one
* @param name the name of the unit. This should be a singular noun (so "Meter", not "Meters")
* @param symbol the short symbol for the unit, such as "m" for meters or "lb." for pounds
*/
@SuppressWarnings("unchecked")
protected Unit(
Class<? extends U> baseType,
U baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
m_baseType = Objects.requireNonNull(baseType);
m_baseUnit = baseUnit == null ? (U) this : baseUnit;
m_toBaseConverter = Objects.requireNonNull(toBaseConverter);
m_fromBaseConverter = Objects.requireNonNull(fromBaseConverter);
m_name = Objects.requireNonNull(name);
@@ -51,16 +53,42 @@ public class Unit<U extends Unit<U>> {
/**
* Creates a new unit with the given name and multiplier to the base unit.
*
* @param baseType the base type of the unit, e.g. Distance.class for the distance unit
* @param baseUnit the base unit, e.g. Meters for distances
* @param baseUnitEquivalent the multiplier to convert this unit to the base unit of this type.
* For example, meters has a multiplier of 1, mm has a multiplier of 1e3, and km has
* multiplier of 1e-3.
* @param name the name of the unit. This should be a singular noun (so "Meter", not "Meters")
* @param symbol the short symbol for the unit, such as "m" for meters or "lb." for pounds
*/
protected Unit(
Class<? extends U> baseType, double baseUnitEquivalent, String name, String symbol) {
this(baseType, x -> x * baseUnitEquivalent, x -> x / baseUnitEquivalent, name, symbol);
protected Unit(U baseUnit, double baseUnitEquivalent, String name, String symbol) {
this(baseUnit, x -> x * baseUnitEquivalent, x -> x / baseUnitEquivalent, name, symbol);
}
/**
* Gets the base unit of measurement that this unit is derived from. If the unit is the base unit,
* the unit will be returned.
*
* <pre><code>
* Unit baseUnit = new Unit(null, ...);
* baseUnit.getBaseUnit(); // returns baseUnit
*
* Unit derivedUnit = new Unit(baseUnit, ...);
* derivedUnit.getBaseUnit(); // returns baseUnit
* </code></pre>
*
* @return the base unit
*/
public U getBaseUnit() {
return m_baseUnit;
}
/**
* Checks if this unit is the base unit for its own system of measurement.
*
* @return true if this is the base unit, false if not
*/
public boolean isBaseUnit() {
return this.equals(m_baseUnit);
}
/**
@@ -240,7 +268,7 @@ public class Unit<U extends Unit<U>> {
* @return true if both units are equivalent, false if not
*/
public boolean equivalent(Unit<?> other) {
if (this.m_baseType != other.m_baseType) {
if (!getClass().equals(other.getClass())) {
// different unit types, not compatible
return false;
}
@@ -265,15 +293,12 @@ public class Unit<U extends Unit<U>> {
return false;
}
Unit<?> that = (Unit<?>) o;
return m_baseType.equals(that.m_baseType)
&& m_name.equals(that.m_name)
&& m_symbol.equals(that.m_symbol)
&& this.equivalent(that);
return m_name.equals(that.m_name) && m_symbol.equals(that.m_symbol) && this.equivalent(that);
}
@Override
public int hashCode() {
return Objects.hash(m_toBaseConverter, m_fromBaseConverter, m_baseType, m_name, m_symbol);
return Objects.hash(m_toBaseConverter, m_fromBaseConverter, m_name, m_symbol);
}
/**

View File

@@ -9,14 +9,19 @@ import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
/**
* Builder used for easily deriving new units from existing ones.
* Builder used for easily deriving new units from existing ones. When deriving a new unit, the base
* unit class <strong>must</strong> redeclare the constructor {@link Unit#Unit(Unit, UnaryFunction,
* UnaryFunction, String, String) (U, UnaryFunction, UnaryFunction, String, String)}. The unit
* builder class will invoke this constructor automatically and build the new unit. Alternatively,
* new units can be derived by passing an explicit constructor function to {@link
* #make(UnitConstructorFunction)}.
*
* @param <U> the type of the unit
*/
public final class UnitBuilder<U extends Unit<U>> {
private final U m_base;
private UnaryFunction m_fromBase;
private UnaryFunction m_toBase;
private UnaryFunction m_fromBase = UnaryFunction.IDENTITY;
private UnaryFunction m_toBase = UnaryFunction.IDENTITY;
private String m_name;
private String m_symbol;
@@ -180,6 +185,51 @@ public final class UnitBuilder<U extends Unit<U>> {
return toBase(x -> x * aggregation).fromBase(b -> b / aggregation);
}
/** A functional interface for constructing new units without relying on reflection. */
@FunctionalInterface
public interface UnitConstructorFunction<U extends Unit<U>> {
/**
* Creates a new unit instance based on its relation to the base unit of measure.
*
* @param baseUnit the base unit of the unit system
* @param toBaseUnits a function that converts values of the new unit to equivalent values in
* terms of the base unit
* @param fromBaseUnits a function that converts values in the base unit to equivalent values in
* terms of the new unit
* @param name the name of the new unit
* @param symbol the shorthand symbol of the new unit
* @return a new unit
*/
U create(
U baseUnit,
UnaryFunction toBaseUnits,
UnaryFunction fromBaseUnits,
String name,
String symbol);
}
/**
* Creates the new unit based off of the builder methods called prior, passing them to a provided
* constructor function.
*
* @param constructor the function to use to create the new derived unit
* @return the new derived unit
* @throws NullPointerException if the unit conversions, unit name, or unit symbol were not set
*/
public U make(UnitConstructorFunction<U> constructor) {
Objects.requireNonNull(m_fromBase, "fromBase function was not set");
Objects.requireNonNull(m_toBase, "toBase function was not set");
Objects.requireNonNull(m_name, "new unit name was not set");
Objects.requireNonNull(m_symbol, "new unit symbol was not set");
return constructor.create(
m_base.getBaseUnit(),
m_toBase.pipeTo(m_base.getConverterToBase()),
m_base.getConverterFromBase().pipeTo(m_fromBase),
m_name,
m_symbol);
}
/**
* Creates the new unit based off of the builder methods called prior.
*
@@ -188,36 +238,52 @@ public final class UnitBuilder<U extends Unit<U>> {
* @throws RuntimeException if the base unit does not define a constructor accepting the
* conversion functions, unit name, and unit symbol - in that order
*/
@SuppressWarnings("PMD.AvoidAccessibilityAlteration")
@SuppressWarnings({"PMD.AvoidAccessibilityAlteration", "unchecked"})
public U make() {
Objects.requireNonNull(m_fromBase, "fromBase function was not set");
Objects.requireNonNull(m_toBase, "toBase function was not set");
Objects.requireNonNull(m_name, "new unit name was not set");
Objects.requireNonNull(m_symbol, "new unit symbol was not set");
Class<? extends U> baseType = m_base.m_baseType;
try {
Constructor<? extends U> ctor =
baseType.getDeclaredConstructor(
UnaryFunction.class, // toBaseUnits
UnaryFunction.class, // fromBaseUnits
String.class, // name
String.class); // symbol
// need to flag the constructor as accessible so we can use private, package-private, and
// protected constructors
ctor.setAccessible(true);
return ctor.newInstance(
m_toBase.pipeTo(m_base.getConverterToBase()),
m_base.getConverterFromBase().pipeTo(m_fromBase),
m_name,
m_symbol);
} catch (InstantiationException e) {
throw new RuntimeException("Could not instantiate class " + baseType.getName(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not access constructor", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Constructing " + baseType.getName() + " raised an exception", e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("No compatible constructor", e);
}
return make(
(baseUnit, toBaseUnits, fromBaseUnits, name, symbol) -> {
var baseClass = baseUnit.getClass();
try {
var ctor = getConstructor(baseUnit);
return (U) ctor.newInstance(baseUnit, toBaseUnits, fromBaseUnits, name, symbol);
} catch (InstantiationException e) {
throw new RuntimeException("Could not instantiate class " + baseClass.getName(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not access constructor", e);
} catch (InvocationTargetException e) {
throw new RuntimeException(
"Constructing " + baseClass.getName() + " raised an exception", e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(
"No compatible constructor "
+ baseClass.getSimpleName()
+ "("
+ baseClass.getSimpleName()
+ ", UnaryFunction, UnaryFunction, String, String)",
e);
}
});
}
@SuppressWarnings("unchecked")
private static <U extends Unit<U>> Constructor<? extends Unit<U>> getConstructor(U baseUnit)
throws NoSuchMethodException {
var baseClass = baseUnit.getClass();
var ctor =
baseClass.getDeclaredConstructor(
baseClass, // baseUnit
UnaryFunction.class, // toBaseUnits
UnaryFunction.class, // fromBaseUnits
String.class, // name
String.class); // symbol
// need to flag the constructor as accessible so we can use private, package-private,
// and protected constructors
ctor.setAccessible(true);
return (Constructor<? extends Unit<U>>) ctor;
}
}

View File

@@ -14,13 +14,6 @@ public final class Units {
// Pseudo-classes describing the more common units of measure.
/**
* Used as an internal placeholder value when a specific unit type cannot be determined. Do not
* use this directly.
*/
@SuppressWarnings("rawtypes")
public static final Unit AnonymousBaseUnit = new Dimensionless(1, "<?>", "<?>");
// Distance
/** The base unit of distance. */
public static final Distance Meters = BaseUnits.Distance;
@@ -98,7 +91,8 @@ public final class Units {
* A single turn of an object around an external axis. Numerically equivalent to {@link
* #Rotations}, but may be semantically more expressive in certain scenarios.
*/
public static final Angle Revolutions = new Angle(2 * Math.PI, "Revolution", "R");
public static final Angle Revolutions =
derive(Radians).aggregate(2 * Math.PI).named("Revolution").symbol("R").make();
/**
* A single turn of an object around an external axis. Numerically equivalent to a {@link
@@ -110,7 +104,7 @@ public final class Units {
* A single turn of an object around an internal axis. Numerically equivalent to {@link
* #Revolutions}, but may be semantically more expressive in certain scenarios.
*/
public static final Angle Rotations = new Angle(2 * Math.PI, "Rotation", "R"); // alias revolution
public static final Angle Rotations = derive(Revolutions).named("Rotation").symbol("R").make();
/**
* A single turn of an object around an internal axis. Numerically equivalent to a {@link
@@ -510,16 +504,4 @@ public final class Units {
public static <U extends Unit<U>> UnitBuilder<U> derive(Unit<U> unit) {
return new UnitBuilder<>((U) unit);
}
/**
* Returns an anonymous unit for use when a specific unit type is not known. Do not use this
* directly.
*
* @param <U> the dimension of the desired anonymous unit
* @return the anonymous unit
*/
@SuppressWarnings("unchecked")
public static <U extends Unit<U>> U anonymous() {
return (U) AnonymousBaseUnit;
}
}

View File

@@ -116,17 +116,27 @@ public class Velocity<D extends Unit<D>> extends Unit<Velocity<D>> {
@SuppressWarnings({"unchecked", "rawtypes"})
Velocity(D unit, Time period, String name, String symbol) {
super((Class) Velocity.class, unit.toBaseUnits(1) / period.toBaseUnits(1), name, symbol);
super(
unit.isBaseUnit() && period.isBaseUnit()
? null
: combine(unit.getBaseUnit(), period.getBaseUnit()),
unit.toBaseUnits(1) / period.toBaseUnits(1),
name,
symbol);
this.m_unit = unit;
this.m_period = period;
}
@SuppressWarnings({"unchecked", "rawtypes"})
Velocity(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super((Class) Velocity.class, toBaseConverter, fromBaseConverter, name, symbol);
this.m_unit = Units.anonymous();
this.m_period = Units.Seconds;
Velocity<D> baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
this.m_unit = baseUnit.getUnit();
this.m_period = baseUnit.getPeriod();
}
/**

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.units;
import static edu.wpi.first.units.Units.Watts;
/**
* Unit of electric voltage dimension.
*
@@ -14,13 +16,17 @@ package edu.wpi.first.units;
* {@link Units} class.
*/
public class Voltage extends Unit<Voltage> {
Voltage(double baseUnitEquivalent, String name, String symbol) {
super(Voltage.class, baseUnitEquivalent, name, symbol);
Voltage(Voltage baseUnit, double baseUnitEquivalent, String name, String symbol) {
super(baseUnit, baseUnitEquivalent, name, symbol);
}
Voltage(
UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, String symbol) {
super(Voltage.class, toBaseConverter, fromBaseConverter, name, symbol);
Voltage baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
}
/**
@@ -35,6 +41,6 @@ public class Voltage extends Unit<Voltage> {
* @return the power unit
*/
public Power times(Unit<Current> current, String name, String symbol) {
return new Power(toBaseUnits(1) * current.toBaseUnits(1), name, symbol);
return new Power(Watts, toBaseUnits(1) * current.toBaseUnits(1), name, symbol);
}
}

View File

@@ -12,9 +12,10 @@ import org.junit.jupiter.api.Test;
class DistanceTest {
@Test
void testBaseUnitDistancePerTime() {
Velocity<Distance> anonBaseUnit = new Distance(1, "D", "d").per(new Time(1, "T", "t"));
Velocity<Distance> anonBaseUnit =
new Distance(null, 1, "D", "d").per(new Time(null, 1, "T", "t"));
assertTrue(BaseUnits.Velocity.equivalent(anonBaseUnit));
assertTrue(Units.MetersPerSecond.equivalent(anonBaseUnit));
}
@Test

View File

@@ -9,11 +9,16 @@ class ExampleUnit extends Unit<ExampleUnit> {
this(baseUnitEquivalent, "Example", "ex");
}
ExampleUnit(UnaryFunction toBase, UnaryFunction fromBase, String name, String symbol) {
super(ExampleUnit.class, toBase, fromBase, name, symbol);
ExampleUnit(
ExampleUnit baseUnit,
UnaryFunction toBase,
UnaryFunction fromBase,
String name,
String symbol) {
super(baseUnit, toBase, fromBase, name, symbol);
}
ExampleUnit(double baseUnitEquivalent, String name, String symbol) {
super(ExampleUnit.class, baseUnitEquivalent, name, symbol);
super(null, baseUnitEquivalent, name, symbol);
}
}

View File

@@ -135,7 +135,7 @@ class UnitsTest {
assertEquals(9.80665, Gs.of(1).in(MetersPerSecondPerSecond), thresh);
assertEquals("G", Gs.name());
assertEquals("G", Gs.symbol());
assertEquals(Units.AnonymousBaseUnit, Gs.getUnit());
assertEquals(MetersPerSecond, Gs.getUnit());
assertEquals(Seconds, Gs.getPeriod());
}

View File

@@ -20,8 +20,8 @@ import org.junit.jupiter.api.Test;
class VelocityTest {
@Test
void testBaseUnit() {
assertTrue(MetersPerSecond.equivalent(BaseUnits.Velocity));
assertTrue(Meters.per(Second).equivalent(BaseUnits.Velocity));
assertTrue(MetersPerSecond.equivalent(MetersPerSecond));
assertTrue(Meters.per(Second).equivalent(MetersPerSecond));
}
@Test