/** * Copyright (C) 2019, 2021, 2022, 2024, 2025 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package sevenUnits.unit; import java.util.Objects; import java.util.Optional; import sevenUnits.utils.NameSymbol; /** * A value expressed in a unit. * * Unless otherwise indicated, all methods in this class throw a * {@code NullPointerException} when an argument is null. * * @author Adrien Hopkins * @since 2020-07-26 */ public final class UnitValue { /** * Creates a {@code UnitValue} from a unit and the associated value. * * @param unit unit to use * @param value value to use * @return {@code UnitValue} instance */ public static UnitValue of(Unit unit, double value) { return new UnitValue( Objects.requireNonNull(unit, "unit must not be null"), value); } private final Unit unit; private final double value; /** * @param unit the unit being used * @param value the value being represented */ private UnitValue(Unit unit, Double value) { this.unit = unit; this.value = value; } /** * @param other unit to try to convert to * @return true if this value can be converted to {@code other}. * @since 2020-10-01 */ public final boolean canConvertTo(Unit other) { return this.unit.canConvertTo(other); } /** * Returns a UnitValue that represents the same value expressed in a * different unit * * @param other new unit to express value in * @return value expressed in {@code other} */ public final UnitValue convertTo(Unit other) { return UnitValue.of(other, this.getUnit().convertTo(other, this.getValue())); } /** * Returns this unit value represented as a {@link LinearUnitValue} with this * unit's base unit as the base. * * @param ns name and symbol for the base unit, use NameSymbol.EMPTY if not * needed. * @return this unit as a {@link LinearUnitValue} * @since 2020-09-29 */ public final LinearUnitValue convertToBase(NameSymbol ns) { final LinearUnit base = LinearUnit.getBase(this.unit).withName(ns); return this.convertToLinear(base); } /** * @param newUnit unit to use for this value * @return a {@code LinearUnitValue} that is equivalent to this value. It * will have zero uncertainty. * @since 2020-09-29 */ public final LinearUnitValue convertToLinear(LinearUnit newUnit) { return LinearUnitValue.getExact(newUnit, this.getUnit().convertTo(newUnit, this.getValue())); } /** * Returns true if this and obj represent the same value, regardless of * whether or not they are expressed in the same unit. So (1000 m).equals(1 * km) returns true. */ @Override public boolean equals(Object obj) { if (!(obj instanceof UnitValue)) return false; final UnitValue other = (UnitValue) obj; return Objects.equals(this.getUnit().getBase(), other.getUnit().getBase()) && Double.doubleToLongBits( this.getUnit().convertToBase(this.getValue())) == Double .doubleToLongBits( other.getUnit().convertToBase(other.getValue())); } /** * @return the unit * @since 2020-09-29 */ public final Unit getUnit() { return this.unit; } /** * @return the value * @since 2020-09-29 */ public final double getValue() { return this.value; } @Override public int hashCode() { return Objects.hash(this.getUnit().getBase(), this.getUnit().convertFromBase(this.getValue())); } @Override public String toString() { final Optional primaryName = this.getUnit().getPrimaryName(); final Optional symbol = this.getUnit().getSymbol(); if (primaryName.isEmpty() && symbol.isEmpty()) { final double baseValue = this.getUnit().convertToBase(this.getValue()); return String.format("%s unnamed unit (= %s %s)", this.getValue(), baseValue, this.getUnit().getBase() .toString(unit -> unit.getSymbol().orElseThrow())); } else { final String unitName = symbol.orElse(primaryName.get()); return this.getValue() + " " + unitName; } } }