mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user