[wpiunits] Java units API rewrite (#6958)

Java generics are too limited to do what we need. This refactors generic code previously in Unit and Measure into unit-specific classes that can have unit-safe math operations (notably, times and divide) that can return values in known units instead of a wildcarded Measure<?>.

Unit-specific measure implementations are automatically generated by ./wpiunits/generate_units.py, which generates generic interfaces and mutable and immutable implementations of those interfaces. These make up the bulk of the diff of this PR (approximately 9300 LOC).

This also adds units for angular and linear velocities, accelerations, and momenta; moment of inertia; and torque.
This commit is contained in:
Sam Carlberg
2024-09-07 13:59:29 -04:00
committed by GitHub
parent 496e7c1bba
commit a9b885070e
178 changed files with 14750 additions and 2158 deletions

View File

@@ -11,21 +11,19 @@ import java.util.Objects;
*
* <p>This is the base class for units. Actual units (such as {@link Units#Grams} and {@link
* Units#Meters}) can be found in the {@link Units} class.
*
* @param <U> the self type, e.g. {@code class SomeUnit extends Unit<SomeUnit>}
*/
public class Unit<U extends Unit<U>> {
public abstract class Unit {
private final UnaryFunction m_toBaseConverter;
private final UnaryFunction m_fromBaseConverter;
private final U m_baseUnit;
private Measure<U> m_zero;
private Measure<U> m_one;
private final Unit m_baseUnit;
private final String m_name;
private final String m_symbol;
private final Measure<?> m_zero;
private final Measure<?> m_one;
/**
* Creates a new unit defined by its relationship to some base unit.
*
@@ -36,18 +34,21 @@ public class Unit<U extends Unit<U>> {
* @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")
@SuppressWarnings("this-escape")
protected Unit(
U baseUnit,
Unit baseUnit,
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
m_baseUnit = baseUnit == null ? (U) this : baseUnit;
m_baseUnit = baseUnit == null ? this : baseUnit;
m_toBaseConverter = Objects.requireNonNull(toBaseConverter);
m_fromBaseConverter = Objects.requireNonNull(fromBaseConverter);
m_name = Objects.requireNonNull(name);
m_symbol = Objects.requireNonNull(symbol);
m_zero = of(0);
m_one = of(1);
}
/**
@@ -60,14 +61,79 @@ public class Unit<U extends Unit<U>> {
* @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(U baseUnit, double baseUnitEquivalent, String name, String symbol) {
protected Unit(Unit baseUnit, double baseUnitEquivalent, String name, String symbol) {
this(baseUnit, x -> x * baseUnitEquivalent, x -> x / baseUnitEquivalent, name, symbol);
}
/**
* Creates a new immutable measurement of the given magnitude in terms of this unit.
* Implementations are <strong>strongly</strong> recommended to sharpen the return type to a
* unit-specific measurement implementation.
*
* @param magnitude the magnitude of the measurement.
* @return the measurement object
*/
public abstract Measure<?> of(double magnitude);
/**
* Creates a new immutable measurement of the given magnitude in terms of this unit's base unit.
* Implementations are <strong>strongly</strong> recommended to sharpen the return type to a
* unit-specific measurement implementation.
*
* @param baseUnitMagnitude the magnitude in terms of the base unit
* @return the measurement object
*/
public abstract Measure<?> ofBaseUnits(double baseUnitMagnitude);
/**
* Creates a new mutable measurement that is initialized to the given magnitude in terms of this
* unit. Implementations are <strong>strongly</strong> recommended to sharpen the return type to a
* unit-specific measurement implementation.
*
* @param initialMagnitude the initial magnitude of the mutable measurement
* @return the mutable measurement object
*/
public abstract MutableMeasure<?, ?, ?> mutable(double initialMagnitude);
/**
* Gets a measure of zero magnitude in terms of this unit. The returned object is guaranteed to be
* of the same type returned by {@link #of(double)}. Subclasses are encouraged to override this
* method to sharpen the return type.
*
* @return a zero-magnitude measure of this unit
*/
public Measure<?> zero() {
return m_zero;
}
/**
* Gets a measure with a magnitude of 1.0 in terms of this unit. The returned object is guaranteed
* to be of the same type returned by {@link #of(double)}. Subclasses are encouraged to override
* this method to sharpen the return type.
*
* @return a measure of magnitude 1.0 in terms of this unit
*/
public Measure<?> one() {
return m_one;
}
/**
* Combines this unit with a unit of time. This often - but not always - results in a velocity.
* Subclasses should sharpen the return type to be unit-specific.
*
* @param time the unit of time
* @return the combined unit
*/
public abstract Unit per(TimeUnit time);
/**
* Gets the base unit of measurement that this unit is derived from. If the unit is the base unit,
* the unit will be returned.
*
* <p><strong>NOTE:</strong> Subclasses <strong>must</strong> override this method to provide the
* correct return type. Failing to do say will make unit combinations that use it break at
* runtime!
*
* <pre><code>
* Unit baseUnit = new Unit(null, ...);
* baseUnit.getBaseUnit(); // returns baseUnit
@@ -78,7 +144,7 @@ public class Unit<U extends Unit<U>> {
*
* @return the base unit
*/
public U getBaseUnit() {
public Unit getBaseUnit() {
return m_baseUnit;
}
@@ -111,27 +177,6 @@ public class Unit<U extends Unit<U>> {
return m_toBaseConverter.apply(valueInNativeUnits);
}
/**
* Converts a magnitude in terms of another unit of the same dimension to a magnitude in terms of
* this unit.
*
* <pre>
* Inches.convertFrom(12, Feet) // 144.0
* Kilograms.convertFrom(2.2, Pounds) // 0.9979024
* </pre>
*
* @param magnitude a magnitude measured in another unit
* @param otherUnit the unit to convert the magnitude to
* @return the corresponding value in terms of this unit.
*/
public double convertFrom(double magnitude, Unit<U> otherUnit) {
if (this.equivalent(otherUnit)) {
// same unit, don't bother converting
return magnitude;
}
return this.fromBaseUnits(otherUnit.toBaseUnits(magnitude));
}
/**
* Gets the conversion function used to convert values to base unit terms. This generally
* shouldn't need to be used directly; prefer {@link #toBaseUnits(double)} instead.
@@ -152,113 +197,6 @@ public class Unit<U extends Unit<U>> {
return m_fromBaseConverter;
}
/**
* Creates a new measure of this unit with the given value. The resulting measure is
* <i>immutable</i> and cannot have its value modified.
*
* @param magnitude the magnitude of the measure to create
* @return the measure
*/
public Measure<U> of(double magnitude) {
if (magnitude == 0) {
// reuse static object
return zero();
}
if (magnitude == 1) {
// reuse static object
return one();
}
return ImmutableMeasure.ofRelativeUnits(magnitude, this);
}
/**
* Creates a new measure with a magnitude equal to the given base unit magnitude, converted to be
* in terms of this unit.
*
* @param baseUnitMagnitude the magnitude of the measure in terms of the base unit
* @return the measure
*/
public Measure<U> ofBaseUnits(double baseUnitMagnitude) {
return ImmutableMeasure.ofBaseUnits(baseUnitMagnitude, this);
}
/**
* Gets a measure with a magnitude of 0 in terms of this unit.
*
* @return the zero-valued measure
*/
public Measure<U> zero() {
// lazy init because 'this' is null in object initialization
if (m_zero == null) {
m_zero = ImmutableMeasure.ofRelativeUnits(0, this);
}
return m_zero;
}
/**
* Gets a measure with a magnitude of 1 in terms of this unit.
*
* @return the 1-valued measure
*/
public Measure<U> one() {
// lazy init because 'this' is null in object initialization
if (m_one == null) {
m_one = ImmutableMeasure.ofRelativeUnits(1, this);
}
return m_one;
}
/**
* Creates a velocity unit derived from this one. Can be chained to denote velocity, acceleration,
* jerk, etc.
*
* <pre>
* Meters.per(Second) // linear velocity
* Kilograms.per(Second) // mass flow
* Feet.per(Second).per(Second).of(32) // roughly 1G of acceleration
* </pre>
*
* @param period the time period of the velocity, such as seconds or milliseconds
* @return a velocity unit corresponding to the rate of change of this unit over time
*/
public Velocity<U> per(Time period) {
return Velocity.combine(this, period);
}
/**
* Takes this unit and creates a new proportional unit where this unit is the numerator and the
* given denominator is the denominator.
*
* <pre>
* Volts.per(Meter) // V/m
* </pre>
*
* @param <D> the type of the denominator units
* @param denominator the denominator of the proportional unit
* @return a combined proportional unit
*/
@SuppressWarnings("unchecked")
public <D extends Unit<D>> Per<U, D> per(D denominator) {
return Per.combine((U) this, denominator);
}
/**
* Takes this unit and creates a new combinatory unit equivalent to this unit multiplied by
* another.
*
* <pre>
* Volts.mult(Meter) // V*m
* </pre>
*
* @param <U2> the type of the unit to multiply by
* @param other the unit to multiply by
* @return a combined unit equivalent to this unit multiplied by the other
*/
@SuppressWarnings("unchecked")
public <U2 extends Unit<U2>> Mult<U, U2> mult(U2 other) {
return Mult.combine((U) this, other);
}
/**
* Checks if this unit is equivalent to another one. Equivalence is determined by both units
* having the same base type and treat the same base unit magnitude as the same magnitude in their
@@ -267,7 +205,7 @@ public class Unit<U extends Unit<U>> {
* @param other the unit to compare to.
* @return true if both units are equivalent, false if not
*/
public boolean equivalent(Unit<?> other) {
public boolean equivalent(Unit other) {
if (!getClass().equals(other.getClass())) {
// different unit types, not compatible
return false;
@@ -287,7 +225,7 @@ public class Unit<U extends Unit<U>> {
@Override
public boolean equals(Object o) {
return this == o
|| o instanceof Unit<?> that
|| o instanceof Unit that
&& m_name.equals(that.m_name)
&& m_symbol.equals(that.m_symbol)
&& this.equivalent(that);