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   * Gets a measure of zero magnitude in terms of this unit. The returned object is guaranteed to be
090   * of the same type returned by {@link #of(double)}. Subclasses are encouraged to override this
091   * method to sharpen the return type.
092   *
093   * @return a zero-magnitude measure of this unit
094   */
095  public Measure<?> zero() {
096    return m_zero;
097  }
098
099  /**
100   * Gets a measure with a magnitude of 1.0 in terms of this unit. The returned object is guaranteed
101   * to be of the same type returned by {@link #of(double)}. Subclasses are encouraged to override
102   * this method to sharpen the return type.
103   *
104   * @return a measure of magnitude 1.0 in terms of this unit
105   */
106  public Measure<?> one() {
107    return m_one;
108  }
109
110  /**
111   * Combines this unit with a unit of time. This often - but not always - results in a velocity.
112   * Subclasses should sharpen the return type to be unit-specific.
113   *
114   * @param time the unit of time
115   * @return the combined unit
116   */
117  public abstract Unit per(TimeUnit time);
118
119  /**
120   * Gets the base unit of measurement that this unit is derived from. If the unit is the base unit,
121   * the unit will be returned.
122   *
123   * <p><strong>NOTE:</strong> Subclasses <strong>must</strong> override this method to provide the
124   * correct return type. Failing to do say will make unit combinations that use it break at
125   * runtime!
126   *
127   * <pre><code>
128   *   Unit baseUnit = new Unit(null, ...);
129   *   baseUnit.getBaseUnit(); // returns baseUnit
130   *
131   *   Unit derivedUnit = new Unit(baseUnit, ...);
132   *   derivedUnit.getBaseUnit(); // returns baseUnit
133   * </code></pre>
134   *
135   * @return the base unit
136   */
137  public Unit getBaseUnit() {
138    return m_baseUnit;
139  }
140
141  /**
142   * Checks if this unit is the base unit for its own system of measurement.
143   *
144   * @return true if this is the base unit, false if not
145   */
146  public boolean isBaseUnit() {
147    return this.equals(m_baseUnit);
148  }
149
150  /**
151   * Converts a value in terms of base units to a value in terms of this unit.
152   *
153   * @param valueInBaseUnits the value in base units to convert
154   * @return the equivalent value in terms of this unit
155   */
156  public double fromBaseUnits(double valueInBaseUnits) {
157    return m_fromBaseConverter.apply(valueInBaseUnits);
158  }
159
160  /**
161   * Converts a value in terms of this unit to a value in terms of the base unit.
162   *
163   * @param valueInNativeUnits the value in terms of this unit to convert
164   * @return the equivalent value in terms of the base unit
165   */
166  public double toBaseUnits(double valueInNativeUnits) {
167    return m_toBaseConverter.apply(valueInNativeUnits);
168  }
169
170  /**
171   * Gets the conversion function used to convert values to base unit terms. This generally
172   * shouldn't need to be used directly; prefer {@link #toBaseUnits(double)} instead.
173   *
174   * @return the conversion function
175   */
176  public UnaryFunction getConverterToBase() {
177    return m_toBaseConverter;
178  }
179
180  /**
181   * Gets the conversion function used to convert values to terms of this unit. This generally
182   * shouldn't need to be used directly; prefer {@link #fromBaseUnits(double)} instead.
183   *
184   * @return the conversion function
185   */
186  public UnaryFunction getConverterFromBase() {
187    return m_fromBaseConverter;
188  }
189
190  /**
191   * Checks if this unit is equivalent to another one. Equivalence is determined by both units
192   * having the same base type and treat the same base unit magnitude as the same magnitude in their
193   * own units, to within {@link Measure#EQUIVALENCE_THRESHOLD}.
194   *
195   * @param other the unit to compare to.
196   * @return true if both units are equivalent, false if not
197   */
198  public boolean equivalent(Unit other) {
199    if (!getClass().equals(other.getClass())) {
200      // different unit types, not compatible
201      return false;
202    }
203
204    double arbitrary = 16_777.214; // 2^24 / 1e3
205
206    return Math.abs(
207                this.m_fromBaseConverter.apply(arbitrary)
208                    - other.m_fromBaseConverter.apply(arbitrary))
209            <= Measure.EQUIVALENCE_THRESHOLD
210        && Math.abs(
211                this.m_toBaseConverter.apply(arbitrary) - other.m_toBaseConverter.apply(arbitrary))
212            <= Measure.EQUIVALENCE_THRESHOLD;
213  }
214
215  @Override
216  public boolean equals(Object o) {
217    return this == o
218        || o instanceof Unit that
219            && m_name.equals(that.m_name)
220            && m_symbol.equals(that.m_symbol)
221            && this.equivalent(that);
222  }
223
224  @Override
225  public int hashCode() {
226    return Objects.hash(m_toBaseConverter, m_fromBaseConverter, m_name, m_symbol);
227  }
228
229  /**
230   * Gets the name of this unit.
231   *
232   * @return the unit's name
233   */
234  public String name() {
235    return m_name;
236  }
237
238  /**
239   * Gets the symbol of this unit.
240   *
241   * @return the unit's symbol
242   */
243  public String symbol() {
244    return m_symbol;
245  }
246
247  @Override
248  public String toString() {
249    return name();
250  }
251}