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;
009import java.util.function.BiFunction;
010
011/**
012 * A helper class for creating and caching combined unit objects. This helps to reduce unnecessary
013 * object allocation by reusing already-created units.
014 *
015 * @param <A> the type of the first unit to be combined
016 * @param <B> the type of the second unit to be combined
017 * @param <Out> the type of the combinatorial unit
018 */
019public final class CombinatoryUnitCache<A extends Unit, B extends Unit, Out extends Unit> {
020  /**
021   * Keep a cache of created instances so expressions like Volts.per(Meter) don't do any allocations
022   * after the first.
023   */
024  private final LongToObjectHashMap<Out> m_cache = new LongToObjectHashMap<>();
025
026  private final BiFunction<? super A, ? super B, ? extends Out> m_constructor;
027
028  /**
029   * Creates a new combinatory unit cache. The cache is initially empty and is not shared across
030   * instances.
031   *
032   * @param constructor the constructor function to use to create new combined units
033   */
034  public CombinatoryUnitCache(BiFunction<? super A, ? super B, ? extends Out> constructor) {
035    this.m_constructor =
036        Objects.requireNonNull(constructor, "Cache unit constructor must be provided");
037  }
038
039  /**
040   * Combines two units together and returns the result. The resulting units are cached and will be
041   * returned on successive calls to avoid allocating many duplicate objects. The combination output
042   * type is determined by the factory function passed into the cache's constructor.
043   *
044   * @param a the first unit
045   * @param b the second unit
046   * @return the combined unit
047   */
048  public Out combine(A a, B b) {
049    final long key = ((long) a.hashCode()) << 32L | (b.hashCode() & 0xFFFFFFFFL);
050
051    var existing = m_cache.get(key);
052    if (existing != null) {
053      return existing;
054    }
055
056    var newUnit = m_constructor.apply(a, b);
057    m_cache.put(key, newUnit);
058    return newUnit;
059  }
060}