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}