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 edu.wpi.first.units.collections.LongToObjectHashMap;
008import java.util.Objects;
009
010/**
011 * Generic combinatory unit type that represents the proportion of one unit to another, such as
012 * Meters per Second or Radians per Celsius.
013 *
014 * <p>Note: {@link Velocity} is used to represent the velocity dimension, rather than {@code
015 * Per<Distance, Time>}.
016 *
017 * @param <N> the type of the numerator unit
018 * @param <D> the type of the denominator unit
019 */
020public class Per<N extends Unit<N>, D extends Unit<D>> extends Unit<Per<N, D>> {
021  private final N m_numerator;
022  private final D m_denominator;
023
024  /**
025   * Keep a cache of created instances so expressions like Volts.per(Meter) don't do any allocations
026   * after the first.
027   */
028  @SuppressWarnings("rawtypes")
029  private static final LongToObjectHashMap<Per> cache = new LongToObjectHashMap<>();
030
031  /**
032   * Creates a new proportional unit derived from the ratio of one unit to another. Consider using
033   * {@link #combine} instead of manually calling this constructor.
034   *
035   * @param numerator the numerator unit
036   * @param denominator the denominator unit
037   */
038  protected Per(N numerator, D denominator) {
039    super(
040        numerator.isBaseUnit() && denominator.isBaseUnit()
041            ? null
042            : combine(numerator.getBaseUnit(), denominator.getBaseUnit()),
043        numerator.toBaseUnits(1) / denominator.toBaseUnits(1),
044        numerator.name() + " per " + denominator.name(),
045        numerator.symbol() + "/" + denominator.symbol());
046    m_numerator = numerator;
047    m_denominator = denominator;
048  }
049
050  Per(
051      Per<N, D> baseUnit,
052      UnaryFunction toBaseConverter,
053      UnaryFunction fromBaseConverter,
054      String name,
055      String symbol) {
056    super(baseUnit, toBaseConverter, fromBaseConverter, name, symbol);
057    m_numerator = baseUnit.numerator();
058    m_denominator = baseUnit.denominator();
059  }
060
061  /**
062   * Creates a new Per unit derived from an arbitrary numerator and time denominator units. Using a
063   * denominator with a unit of time is discouraged; use {@link Velocity} instead.
064   *
065   * <pre>
066   *   Per.combine(Volts, Meters) // possible PID constant
067   * </pre>
068   *
069   * <p>It's recommended to use the convenience function {@link Unit#per(Unit)} instead of calling
070   * this factory directly.
071   *
072   * @param <N> the type of the numerator unit
073   * @param <D> the type of the denominator unit
074   * @param numerator the numerator unit
075   * @param denominator the denominator for unit time
076   * @return the combined unit
077   */
078  @SuppressWarnings("unchecked")
079  public static <N extends Unit<N>, D extends Unit<D>> Per<N, D> combine(
080      N numerator, D denominator) {
081    final long key =
082        ((long) numerator.hashCode()) << 32L | (((long) denominator.hashCode()) & 0xFFFFFFFFL);
083
084    var existing = cache.get(key);
085    if (existing != null) {
086      return existing;
087    }
088
089    var newUnit = new Per<>(numerator, denominator);
090    cache.put(key, newUnit);
091    return newUnit;
092  }
093
094  /**
095   * Gets the numerator unit. For a {@code Per<A, B>}, this will return the {@code A} unit.
096   *
097   * @return the numerator unit
098   */
099  public N numerator() {
100    return m_numerator;
101  }
102
103  /**
104   * Gets the denominator unit. For a {@code Per<A, B>}, this will return the {@code B} unit.
105   *
106   * @return the denominator unit
107   */
108  public D denominator() {
109    return m_denominator;
110  }
111
112  /**
113   * Returns the reciprocal of this Per.
114   *
115   * @return the reciprocal
116   */
117  public Per<D, N> reciprocal() {
118    return m_denominator.per(m_numerator);
119  }
120
121  @Override
122  public boolean equals(Object o) {
123    if (this == o) {
124      return true;
125    }
126    if (o == null || getClass() != o.getClass()) {
127      return false;
128    }
129    if (!super.equals(o)) {
130      return false;
131    }
132    Per<?, ?> per = (Per<?, ?>) o;
133    return Objects.equals(m_numerator, per.m_numerator)
134        && Objects.equals(m_denominator, per.m_denominator);
135  }
136
137  @Override
138  public int hashCode() {
139    return Objects.hash(super.hashCode(), m_numerator, m_denominator);
140  }
141}