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.lang.reflect.Constructor;
008import java.lang.reflect.InvocationTargetException;
009import java.util.Objects;
010
011/**
012 * Builder used for easily deriving new units from existing ones. When deriving a new unit, the base
013 * unit class <strong>must</strong> redeclare the constructor {@link Unit#Unit(Unit, UnaryFunction,
014 * UnaryFunction, String, String) (U, UnaryFunction, UnaryFunction, String, String)}. The unit
015 * builder class will invoke this constructor automatically and build the new unit. Alternatively,
016 * new units can be derived by passing an explicit constructor function to {@link
017 * #make(UnitConstructorFunction)}.
018 *
019 * @param <U> the type of the unit
020 */
021public final class UnitBuilder<U extends Unit<U>> {
022  private final U m_base;
023  private UnaryFunction m_fromBase = UnaryFunction.IDENTITY;
024  private UnaryFunction m_toBase = UnaryFunction.IDENTITY;
025  private String m_name;
026  private String m_symbol;
027
028  /**
029   * Creates a new unit builder object, building off of a base unit. The base unit does not have to
030   * be <i>the</i> base unit of its unit system; furlongs work just as well here as meters.
031   *
032   * @param base the unit to base the new unit off of
033   */
034  public UnitBuilder(U base) {
035    this.m_base = Objects.requireNonNull(base, "Base unit cannot be null");
036  }
037
038  /**
039   * Sets the unit conversions based on a simple offset. The new unit will have its values equal to
040   * (base value - offset).
041   *
042   * @param offset the offset
043   * @return this builder
044   */
045  public UnitBuilder<U> offset(double offset) {
046    m_toBase = derivedValue -> derivedValue + offset;
047    m_fromBase = baseValue -> baseValue - offset;
048    return this;
049  }
050
051  /**
052   * Maps a value {@code value} in the range {@code [inMin..inMax]} to an output in the range {@code
053   * [outMin..outMax]}. Inputs outside the bounds will be mapped correspondingly to outputs outside
054   * the output bounds. Inputs equal to {@code inMin} will be mapped to {@code outMin}, and inputs
055   * equal to {@code inMax} will similarly be mapped to {@code outMax}.
056   *
057   * @param value the value to map
058   * @param inMin the minimum input value (does not have to be absolute)
059   * @param inMax the maximum input value (does not have to be absolute)
060   * @param outMin the minimum output value (does not have to be absolute)
061   * @param outMax the maximum output value (does not have to be absolute)
062   * @return the mapped output
063   */
064  // NOTE: This method lives here instead of in MappingBuilder because inner classes can't
065  // define static methods prior to Java 16.
066  private static double mapValue(
067      double value, double inMin, double inMax, double outMin, double outMax) {
068    return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
069  }
070
071  /** Helper class used for safely chaining mapping builder calls. */
072  public class MappingBuilder {
073    private final double m_minInput;
074    private final double m_maxInput;
075
076    private MappingBuilder(double minInput, double maxInput) {
077      this.m_minInput = minInput;
078      this.m_maxInput = maxInput;
079    }
080
081    /**
082     * Finalizes the mapping by defining the output range.
083     *
084     * @param minOutput the minimum output value (does not have to be absolute)
085     * @param maxOutput the maximum output value (does not have to be absolute)
086     * @return the unit builder, for continued chaining
087     */
088    public UnitBuilder<U> toOutputRange(double minOutput, double maxOutput) {
089      UnitBuilder.this.m_fromBase = x -> mapValue(x, m_minInput, m_maxInput, minOutput, maxOutput);
090      UnitBuilder.this.m_toBase = y -> mapValue(y, minOutput, maxOutput, m_minInput, m_maxInput);
091      return UnitBuilder.this;
092    }
093  }
094
095  /**
096   * Defines a mapping for values within the given input range. This method call should be
097   * immediately followed by {@code .toOutputRange}, eg {@code mappingInputRange(1,
098   * 2).toOutputRange(3, 4)}, which will return the unit builder for continued chaining.
099   *
100   * @param minBase the minimum input value (does not have to be absolute)
101   * @param maxBase the maximum output value (does not have to be absolute)
102   * @return a builder object used to define the output range
103   */
104  public MappingBuilder mappingInputRange(double minBase, double maxBase) {
105    return new MappingBuilder(minBase, maxBase);
106  }
107
108  /**
109   * Sets the conversion function to transform values in the base unit to values in the derived
110   * unit.
111   *
112   * @param fromBase the conversion function
113   * @return the unit builder, for continued chaining
114   */
115  public UnitBuilder<U> fromBase(UnaryFunction fromBase) {
116    this.m_fromBase = Objects.requireNonNull(fromBase, "fromBase function cannot be null");
117    return this;
118  }
119
120  /**
121   * Sets the conversion function to transform values in the derived unit to values in the base
122   * unit.
123   *
124   * @param toBase the conversion function
125   * @return the unit builder, for continued chaining
126   */
127  public UnitBuilder<U> toBase(UnaryFunction toBase) {
128    this.m_toBase = Objects.requireNonNull(toBase, "toBase function cannot be null");
129    return this;
130  }
131
132  /**
133   * Sets the name of the new unit.
134   *
135   * @param name the new name
136   * @return the unit builder, for continued chaining
137   */
138  public UnitBuilder<U> named(String name) {
139    this.m_name = name;
140    return this;
141  }
142
143  /**
144   * Sets the symbol of the new unit.
145   *
146   * @param symbol the new symbol
147   * @return the unit builder, for continued chaining
148   */
149  public UnitBuilder<U> symbol(String symbol) {
150    this.m_symbol = symbol;
151    return this;
152  }
153
154  /**
155   * Helper for defining units that are a scalar fraction of the base unit, such as centimeters
156   * being 1/100th of the base unit (meters). The fraction value is specified as the denominator of
157   * the fraction, so a centimeter definition would use {@code splitInto(100)} instead of {@code
158   * splitInto(1/100.0)}.
159   *
160   * @param fraction the denominator portion of the fraction of the base unit that a value of 1 in
161   *     the derived unit corresponds to
162   * @return the unit builder, for continued chaining
163   */
164  public UnitBuilder<U> splitInto(double fraction) {
165    if (fraction == 0) {
166      throw new IllegalArgumentException("Fraction must be nonzero");
167    }
168
169    return toBase(x -> x / fraction).fromBase(b -> b * fraction);
170  }
171
172  /**
173   * Helper for defining units that are a scalar multiple of the base unit, such as kilometers being
174   * 1000x of the base unit (meters).
175   *
176   * @param aggregation the magnitude required for a measure in the base unit to equal a magnitude
177   *     of 1 in the derived unit
178   * @return the unit builder, for continued chaining
179   */
180  public UnitBuilder<U> aggregate(double aggregation) {
181    if (aggregation == 0) {
182      throw new IllegalArgumentException("Aggregation amount must be nonzero");
183    }
184
185    return toBase(x -> x * aggregation).fromBase(b -> b / aggregation);
186  }
187
188  /**
189   * A functional interface for constructing new units without relying on reflection.
190   *
191   * @param <U> the type of the unit
192   */
193  @FunctionalInterface
194  public interface UnitConstructorFunction<U extends Unit<U>> {
195    /**
196     * Creates a new unit instance based on its relation to the base unit of measure.
197     *
198     * @param baseUnit the base unit of the unit system
199     * @param toBaseUnits a function that converts values of the new unit to equivalent values in
200     *     terms of the base unit
201     * @param fromBaseUnits a function that converts values in the base unit to equivalent values in
202     *     terms of the new unit
203     * @param name the name of the new unit
204     * @param symbol the shorthand symbol of the new unit
205     * @return a new unit
206     */
207    U create(
208        U baseUnit,
209        UnaryFunction toBaseUnits,
210        UnaryFunction fromBaseUnits,
211        String name,
212        String symbol);
213  }
214
215  /**
216   * Creates the new unit based off of the builder methods called prior, passing them to a provided
217   * constructor function.
218   *
219   * @param constructor the function to use to create the new derived unit
220   * @return the new derived unit
221   * @throws NullPointerException if the unit conversions, unit name, or unit symbol were not set
222   */
223  public U make(UnitConstructorFunction<U> constructor) {
224    Objects.requireNonNull(m_fromBase, "fromBase function was not set");
225    Objects.requireNonNull(m_toBase, "toBase function was not set");
226    Objects.requireNonNull(m_name, "new unit name was not set");
227    Objects.requireNonNull(m_symbol, "new unit symbol was not set");
228
229    return constructor.create(
230        m_base.getBaseUnit(),
231        m_toBase.pipeTo(m_base.getConverterToBase()),
232        m_base.getConverterFromBase().pipeTo(m_fromBase),
233        m_name,
234        m_symbol);
235  }
236
237  /**
238   * Creates the new unit based off of the builder methods called prior.
239   *
240   * @return the new derived unit
241   * @throws NullPointerException if the unit conversions, unit name, or unit symbol were not set
242   * @throws RuntimeException if the base unit does not define a constructor accepting the
243   *     conversion functions, unit name, and unit symbol - in that order
244   */
245  @SuppressWarnings({"PMD.AvoidAccessibilityAlteration", "unchecked"})
246  public U make() {
247    return make(
248        (baseUnit, toBaseUnits, fromBaseUnits, name, symbol) -> {
249          var baseClass = baseUnit.getClass();
250
251          try {
252            var ctor = getConstructor(baseUnit);
253
254            return (U) ctor.newInstance(baseUnit, toBaseUnits, fromBaseUnits, name, symbol);
255          } catch (InstantiationException e) {
256            throw new RuntimeException("Could not instantiate class " + baseClass.getName(), e);
257          } catch (IllegalAccessException e) {
258            throw new RuntimeException("Could not access constructor", e);
259          } catch (InvocationTargetException e) {
260            throw new RuntimeException(
261                "Constructing " + baseClass.getName() + " raised an exception", e);
262          } catch (NoSuchMethodException e) {
263            throw new RuntimeException(
264                "No compatible constructor "
265                    + baseClass.getSimpleName()
266                    + "("
267                    + baseClass.getSimpleName()
268                    + ", UnaryFunction, UnaryFunction, String, String)",
269                e);
270          }
271        });
272  }
273
274  @SuppressWarnings("unchecked")
275  private static <U extends Unit<U>> Constructor<? extends Unit<U>> getConstructor(U baseUnit)
276      throws NoSuchMethodException {
277    var baseClass = baseUnit.getClass();
278
279    var ctor =
280        baseClass.getDeclaredConstructor(
281            baseClass, // baseUnit
282            UnaryFunction.class, // toBaseUnits
283            UnaryFunction.class, // fromBaseUnits
284            String.class, // name
285            String.class); // symbol
286
287    // need to flag the constructor as accessible so we can use private, package-private,
288    // and protected constructors
289    ctor.setAccessible(true);
290
291    return (Constructor<? extends Unit<U>>) ctor;
292  }
293}