/**
* 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."));
}
}