001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.units;
006
007import java.util.Objects;
008
009/**
010 * Unit of measurement that defines a quantity, such as grams, meters, or seconds.
011 *
012 * <p>This is the base class for units. Actual units (such as {@link Units#Grams} and {@link
013 * Units#Meters}) can be found in the {@link Units} class.
014 */
015public abstract class Unit {
016  private final UnaryFunction m_toBaseConverter;
017  private final UnaryFunction m_fromBaseConverter;
018
019  private final Unit m_baseUnit;
020
021  private final String m_name;
022  private final String m_symbol;
023
024  private final Measure<?> m_zero;
025  private final Measure<?> m_one;
026
027  /**
028   * Creates a new unit defined by its relationship to some base unit.
029   *
030   * @param baseUnit the base unit, e.g. Meters for distances. Set this to {@code null} if the unit
031   *     being constructed is its own base unit
032   * @param toBaseConverter a function for converting units of this type to the base unit
033   * @param fromBaseConverter a function for converting units of the base unit to this one
034   * @param name the name of the unit. This should be a singular noun (so "Meter", not "Meters")
035   * @param symbol the short symbol for the unit, such as "m" for meters or "lb." for pounds
036   */
037  @SuppressWarnings("this-escape")
038  protected Unit(
039      Unit baseUnit,
040      UnaryFunction toBaseConverter,
041      UnaryFunction fromBaseConverter,
042      String name,
043      String symbol) {
044    m_baseUnit = baseUnit == null ? this : baseUnit;
045    m_toBaseConverter = Objects.requireNonNull(toBaseConverter);
046    m_fromBaseConverter = Objects.requireNonNull(fromBaseConverter);
047    m_name = Objects.requireNonNull(name);
048    m_symbol = Objects.requireNonNull(symbol);
049
050    m_zero = of(0);
051    m_one = of(1);
052  }
053
054  /**
055   * Creates a new unit with the given name and multiplier to the base unit.
056   *
057   * @param baseUnit the base unit, e.g. Meters for distances
058   * @param baseUnitEquivalent the multiplier to convert this unit to the base unit of this type.
059   *     For example, meters has a multiplier of 1, mm has a multiplier of 1e3, and km has
060   *     multiplier of 1e-3.
061   * @param name the name of the unit. This should be a singular noun (so "Meter", not "Meters")
062   * @param symbol the short symbol for the unit, such as "m" for meters or "lb." for pounds
063   */
064  protected Unit(Unit baseUnit, double baseUnitEquivalent, String name, String symbol) {
065    this(baseUnit, x -> x * baseUnitEquivalent, x -> x / baseUnitEquivalent, name, symbol);
066  }
067
068  /**
069   * Creates a new immutable measurement of the given magnitude in terms of this unit.
070   * Implementations are <strong>strongly</strong> recommended to sharpen the return type to a
071   * unit-specific measurement implementation.
072   *
073   * @param magnitude the magnitude of the measurement.
074   * @return the measurement object
075   */
076  public abstract Measure<?> of(double magnitude);
077
078  /**
079   * Creates a new immutable measurement of the given magnitude in terms of this unit's base unit.
080   * Implementations are <strong>strongly</strong> recommended to sharpen the return type to a
081   * unit-specific measurement implementation.
082   *
083   * @param baseUnitMagnitude the magnitude in terms of the base unit
084   * @return the measurement object
085   */
086  public abstract Measure<?> ofBaseUnits(double baseUnitMagnitude);
087
088  /**
089   * Creates a new mutable measurement that is initialized to the given magnitude in terms of this
090   * unit. Implementations are <strong>strongly</strong> recommended to sharpen the return type to a
091   * unit-specific measurement implementation.
092   *
093   * @param initialMagnitude the initial magnitude of the mutable measurement
094   * @return the mutable measurement object
095   */
096  public abstract MutableMeasure<?, ?, ?> mutable(double initialMagnitude);
097
098  /**
099   * Gets a measure of zero magnitude in terms of this unit. The returned object is guaranteed to be
100   * of the same type returned by {@link #of(double)}. Subclasses are encouraged to override this
101   * method to sharpen the return type.
102   *
103   * @return a zero-magnitude measure of this unit
104   */
105  public Measure<?> zero() {
106    return m_zero;
107  }
108
109  /**
110   * Gets a measure with a magnitude of 1.0 in terms of this unit. The returned object is guaranteed
111   * to be of the same type returned by {@link #of(double)}. Subclasses are encouraged to override
112   * this method to sharpen the return type.
113   *
114   * @return a measure of magnitude 1.0 in terms of this unit
115   */
116  public Measure<?> one() {
117    return m_one;
118  }
119
120  /**
121   * Combines this unit with a unit of time. This often - but not always - results in a velocity.
122   * Subclasses should sharpen the return type to be unit-specific.
123   *
124   * @param time the unit of time
125   * @return the combined unit
126   */
127  public abstract Unit per(TimeUnit time);
128
129  /**
130   * Gets the base unit of measurement that this unit is derived from. If the unit is the base unit,
131   * the unit will be returned.
132   *
133   * <p><strong>NOTE:</strong> Subclasses <strong>must</strong> override this method to provide the
134   * correct return type. Failing to do say will make unit combinations that use it break at
135   * runtime!
136   *
137   * <pre><code>
138   *   Unit baseUnit = new Unit(null, ...);
139   *   baseUnit.getBaseUnit(); // returns baseUnit
140   *
141   *   Unit derivedUnit = new Unit(baseUnit, ...);
142   *   derivedUnit.getBaseUnit(); // returns baseUnit
143   * </code></pre>
144   *
145   * @return the base unit
146   */
147  public Unit getBaseUnit() {
148    return m_baseUnit;
149  }
150
151  /**
152   * Checks if this unit is the base unit for its own system of measurement.
153   *
154   * @return true if this is the base unit, false if not
155   */
156  public boolean isBaseUnit() {
157    return this.equals(m_baseUnit);
158  }
159
160  /**
161   * Converts a value in terms of base units to a value in terms of this unit.
162   *
163   * @param valueInBaseUnits the value in base units to convert
164   * @return the equivalent value in terms of this unit
165   */
166  public double fromBaseUnits(double valueInBaseUnits) {
167    return m_fromBaseConverter.apply(valueInBaseUnits);
168  }
169
170  /**
171   * Converts a value in terms of this unit to a value in terms of the base unit.
172   *
173   * @param valueInNativeUnits the value in terms of this unit to convert
174   * @return the equivalent value in terms of the base unit
175   */
176  public double toBaseUnits(double valueInNativeUnits) {
177    return m_toBaseConverter.apply(valueInNativeUnits);
178  }
179
180  /**
181   * Gets the conversion function used to convert values to base unit terms. This generally
182   * shouldn't need to be used directly; prefer {@link #toBaseUnits(double)} instead.
183   *
184   * @return the conversion function
185   */
186  public UnaryFunction getConverterToBase() {
187    return m_toBaseConverter;
188  }
189
190  /**
191   * Gets the conversion function used to convert values to terms of this unit. This generally
192   * shouldn't need to be used directly; prefer {@link #fromBaseUnits(double)} instead.
193   *
194   * @return the conversion function
195   */
196  public UnaryFunction getConverterFromBase() {
197    return m_fromBaseConverter;
198  }
199
200  /**
201   * Checks if this unit is equivalent to another one. Equivalence is determined by both units
202   * having the same base type and treat the same base unit magnitude as the same magnitude in their
203   * own units, to within {@link Measure#EQUIVALENCE_THRESHOLD}.
204   *
205   * @param other the unit to compare to.
206   * @return true if both units are equivalent, false if not
207   */
208  public boolean equivalent(Unit other) {
209    if (!getClass().equals(other.getClass())) {
210      // different unit types, not compatible
211      return false;
212    }
213
214    double arbitrary = 16_777.214; // 2^24 / 1e3
215
216    return Math.abs(
217                this.m_fromBaseConverter.apply(arbitrary)
218                    - other.m_fromBaseConverter.apply(arbitrary))
219            <= Measure.EQUIVALENCE_THRESHOLD
220        && Math.abs(
221                this.m_toBaseConverter.apply(arbitrary) - other.m_toBaseConverter.apply(arbitrary))
222            <= Measure.EQUIVALENCE_THRESHOLD;
223  }
224
225  @Override
226  public boolean equals(Object o) {
227    return this == o
228        || o instanceof Unit that
229            && m_name.equals(that.m_name)
230            && m_symbol.equals(that.m_symbol)
231            && this.equivalent(that);
232  }
233
234  @Override
235  public int hashCode() {
236    return Objects.hash(m_toBaseConverter, m_fromBaseConverter, m_name, m_symbol);
237  }
238
239  /**
240   * Gets the name of this unit.
241   *
242   * @return the unit's name
243   */
244  public String name() {
245    return m_name;
246  }
247
248  /**
249   * Gets the symbol of this unit.
250   *
251   * @return the unit's symbol
252   */
253  public String symbol() {
254    return m_symbol;
255  }
256
257  @Override
258  public String toString() {
259    return name();
260  }
261}