/** * Copyright (C) 2020 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 org.unitConverter.unit; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.DoubleFunction; import java.util.function.ToDoubleFunction; import org.unitConverter.math.ObjectProduct; /** * An object that can convert a value between multiple forms (instances of the * object); like a unit but the "converted value" can be any type. * * @since 2020-09-07 */ public abstract class Unitlike implements Nameable { /** * Returns a unitlike form from its base and the functions it uses to convert * to and from its base. * * @param base unitlike form's base * @param converterFrom function that accepts a value expressed in the * unitlike form's base and returns that value expressed * in this unitlike form. * @param converterTo function that accepts a value expressed in the * unitlike form and returns that value expressed in the * unit's base. * @return a unitlike form that uses the provided functions to convert. * @since 2020-09-07 * @throws NullPointerException if any argument is null */ public static final Unitlike fromConversionFunctions( final ObjectProduct base, final DoubleFunction converterFrom, final ToDoubleFunction converterTo) { return new FunctionalUnitlike<>(base, NameSymbol.EMPTY, converterFrom, converterTo); } /** * Returns a unitlike form from its base and the functions it uses to convert * to and from its base. * * @param base unitlike form's base * @param converterFrom function that accepts a value expressed in the * unitlike form's base and returns that value expressed * in this unitlike form. * @param converterTo function that accepts a value expressed in the * unitlike form and returns that value expressed in the * unit's base. * @param ns names and symbol of unit * @return a unitlike form that uses the provided functions to convert. * @since 2020-09-07 * @throws NullPointerException if any argument is null */ public static final Unitlike fromConversionFunctions( final ObjectProduct base, final DoubleFunction converterFrom, final ToDoubleFunction converterTo, final NameSymbol ns) { return new FunctionalUnitlike<>(base, ns, converterFrom, converterTo); } /** * The combination of units that this unit is based on. * * @since 2019-10-16 */ private final ObjectProduct unitBase; /** * This unit's name(s) and symbol * * @since 2020-09-07 */ private final NameSymbol nameSymbol; /** * Cache storing the result of getDimension() * * @since 2019-10-16 */ private transient ObjectProduct dimension = null; /** * @param unitBase * @since 2020-09-07 */ Unitlike(ObjectProduct unitBase, NameSymbol ns) { this.unitBase = Objects.requireNonNull(unitBase, "unitBase may not be null"); this.nameSymbol = Objects.requireNonNull(ns, "ns may not be null"); } /** * Checks if a value expressed in this unitlike form can be converted to a * value expressed in {@code other} * * @param other unit or unitlike form to test with * @return true if they are compatible * @since 2019-01-13 * @since v0.1.0 * @throws NullPointerException if other is null */ public final boolean canConvertTo(final Unit other) { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } /** * Checks if a value expressed in this unitlike form can be converted to a * value expressed in {@code other} * * @param other unit or unitlike form to test with * @return true if they are compatible * @since 2019-01-13 * @since v0.1.0 * @throws NullPointerException if other is null */ public final boolean canConvertTo(final Unitlike other) { Objects.requireNonNull(other, "other must not be null."); return Objects.equals(this.getBase(), other.getBase()); } protected abstract V convertFromBase(double value); /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. * * @implSpec If conversion is possible, this implementation returns * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. * * @param other unit to convert to * @param value value to convert * @return converted value * @since 2019-05-22 * @throws IllegalArgumentException if {@code other} is incompatible for * conversion with this unitlike form (as * tested by {@link Unit#canConvertTo}). * @throws NullPointerException if other is null */ public final double convertTo(final Unit other, final V value) { Objects.requireNonNull(other, "other must not be null."); if (this.canConvertTo(other)) return other.convertFromBase(this.convertToBase(value)); else throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } /** * Converts a value expressed in this unitlike form to a value expressed in * {@code other}. * * @implSpec If conversion is possible, this implementation returns * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. * * @param other unitlike form to convert to * @param value value to convert * @param type of value to convert to * @return converted value * @since 2020-09-07 * @throws IllegalArgumentException if {@code other} is incompatible for * conversion with this unitlike form (as * tested by {@link Unit#canConvertTo}). * @throws NullPointerException if other is null */ public final W convertTo(final Unitlike other, final V value) { Objects.requireNonNull(other, "other must not be null."); if (this.canConvertTo(other)) return other.convertFromBase(this.convertToBase(value)); else throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); } protected abstract double convertToBase(V value); /** * @return combination of units that this unit is based on * @since 2018-12-22 * @since v0.1.0 */ public final ObjectProduct getBase() { return this.unitBase; } /** * @return dimension measured by this unit * @since 2018-12-22 * @since v0.1.0 */ public final ObjectProduct getDimension() { if (this.dimension == null) { final Map mapping = this.unitBase.exponentMap(); final Map dimensionMap = new HashMap<>(); for (final BaseUnit key : mapping.keySet()) { dimensionMap.put(key.getBaseDimension(), mapping.get(key)); } this.dimension = ObjectProduct.fromExponentMapping(dimensionMap); } return this.dimension; } /** * @return the nameSymbol * @since 2020-09-07 */ @Override public final NameSymbol getNameSymbol() { return this.nameSymbol; } @Override public String toString() { return this.getPrimaryName().orElse("Unnamed unitlike form") + (this.getSymbol().isPresent() ? String.format(" (%s)", this.getSymbol().get()) : "") + ", derived from " + this.getBase().toString(u -> u.getSymbol().get()) + (this.getOtherNames().isEmpty() ? "" : ", also called " + String.join(", ", this.getOtherNames())); } /** * @param ns name(s) and symbol to use * @return a copy of this unitlike form with provided name(s) and symbol * @since 2020-09-07 * @throws NullPointerException if ns is null */ public Unitlike withName(final NameSymbol ns) { return fromConversionFunctions(this.getBase(), this::convertFromBase, this::convertToBase, Objects.requireNonNull(ns, "ns must not be null.")); } }