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.
015 *
016 * @param <N> the type of the numerator unit
017 * @param <D> the type of the denominator unit
018 */
019public class Per<N extends Unit<N>, D extends Unit<D>> extends Unit<Per<N, D>> {
020  private final N m_numerator;
021  private final D m_denominator;
022
023  /**
024   * Keep a cache of created instances so expressions like Volts.per(Meter) don't do any allocations
025   * after the first.
026   */
027  @SuppressWarnings("rawtypes")
028  private static final LongToObjectHashMap<Per> cache = new LongToObjectHashMap<>();
029
030  protected Per(Class<Per<N, D>> baseType, N numerator, D denominator) {
031    super(
032        baseType,
033        numerator.toBaseUnits(1) / denominator.toBaseUnits(1),
034        numerator.name() + " per " + denominator.name(),
035        numerator.symbol() + "/" + denominator.symbol());
036    m_numerator = numerator;
037    m_denominator = denominator;
038  }
039
040  /**
041   * Creates a new Per unit derived from an arbitrary numerator and time denominator units. Using a
042   * denominator with a unit of time is discouraged; use {@link Velocity} instead.
043   *
044   * <pre>
045   *   Per.combine(Volts, Meters) // possible PID constant
046   * </pre>
047   *
048   * <p>It's recommended to use the convenience function {@link Unit#per(Unit)} instead of calling
049   * this factory directly.
050   *
051   * @param <N> the type of the numerator unit
052   * @param <D> the type of the denominator unit
053   * @param numerator the numerator unit
054   * @param denominator the denominator for unit time
055   * @return the combined unit
056   */
057  @SuppressWarnings({"unchecked", "rawtypes"})
058  public static <N extends Unit<N>, D extends Unit<D>> Per<N, D> combine(
059      N numerator, D denominator) {
060    final long key =
061        ((long) numerator.hashCode()) << 32L | ((long) denominator.hashCode()) & 0xFFFFFFFFL;
062
063    var existing = cache.get(key);
064    if (existing != null) {
065      return existing;
066    }
067
068    var newUnit = new Per<N, D>((Class) Per.class, numerator, denominator);
069    cache.put(key, newUnit);
070    return newUnit;
071  }
072
073  public N numerator() {
074    return m_numerator;
075  }
076
077  public D denominator() {
078    return m_denominator;
079  }
080
081  @Override
082  public boolean equals(Object o) {
083    if (this == o) {
084      return true;
085    }
086    if (o == null || getClass() != o.getClass()) {
087      return false;
088    }
089    if (!super.equals(o)) {
090      return false;
091    }
092    Per<?, ?> per = (Per<?, ?>) o;
093    return Objects.equals(m_numerator, per.m_numerator)
094        && Objects.equals(m_denominator, per.m_denominator);
095  }
096
097  @Override
098  public int hashCode() {
099    return Objects.hash(super.hashCode(), m_numerator, m_denominator);
100  }
101}