tec.uom.se.unit.ProductUnit Maven / Gradle / Ivy
/*
* Units of Measurement Implementation for Java SE
* Copyright (c) 2005-2018, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of JSR-363 nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tec.uom.se.unit;
import tec.uom.se.AbstractConverter;
import tec.uom.se.AbstractUnit;
import tec.uom.se.quantity.QuantityDimension;
import javax.measure.Dimension;
import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.UnitConverter;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
*
* This class represents units formed by the product of rational powers of existing physical units.
*
*
*
* This class maintains the canonical form of this product (simplest form after factorization). For example: METRE.pow(2).divide(METRE)
* returns METRE
.
*
*
* @param
* The type of the quantity measured by this unit.
*
* @author Jean-Marie Dautelle
* @author Werner Keil
* @version 1.0.4, November 7, 2017
* @since 1.0
*/
public final class ProductUnit> extends AbstractUnit {
/**
*
*/
private static final long serialVersionUID = 962983585531030093L;
/**
* Holds the units composing this product unit.
*/
private final Element[] elements;
/**
* Holds the symbol for this unit.
*/
private final String symbol;
/**
* DefaultQuantityFactory constructor (used solely to create ONE
instance).
*/
public ProductUnit() {
this.symbol = "";
elements = new Element[0];
}
/**
* Copy constructor (allows for parameterization of product units).
*
* @param productUnit
* the product unit source.
* @throws ClassCastException
* if the specified unit is not a product unit.
*/
public ProductUnit(Unit productUnit) {
this.symbol = productUnit.getSymbol();
this.elements = ((ProductUnit) productUnit).elements;
}
/**
* Product unit constructor.
*
* @param elements
* the product elements.
*/
private ProductUnit(Element[] elements) {
this.elements = elements;
// this.symbol = elements[0].getUnit().getSymbol(); // FIXME this should contain ALL elements
this.symbol = null;
}
/**
* Returns the product of the specified units.
*
* @param left
* the left unit operand.
* @param right
* the right unit operand.
* @return left * right
*/
public static AbstractUnit getProductInstance(Unit left, Unit right) {
Element[] leftElems;
if (left instanceof ProductUnit) {
leftElems = ((ProductUnit) left).elements;
} else {
leftElems = new Element[] { new Element(left, 1, 1) };
}
Element[] rightElems;
if (right instanceof ProductUnit) {
rightElems = ((ProductUnit) right).elements;
} else {
rightElems = new Element[] { new Element(right, 1, 1) };
}
return getInstance(leftElems, rightElems);
}
/**
* Returns the quotient of the specified units.
*
* @param left
* the dividend unit operand.
* @param right
* the divisor unit operand.
* @return dividend / divisor
*/
public static AbstractUnit getQuotientInstance(Unit left, Unit right) {
Element[] leftElems;
if (left instanceof ProductUnit)
leftElems = ((ProductUnit) left).elements;
else
leftElems = new Element[] { new Element(left, 1, 1) };
Element[] rightElems;
if (right instanceof ProductUnit) {
Element[] elems = ((ProductUnit) right).elements;
rightElems = new Element[elems.length];
for (int i = 0; i < elems.length; i++) {
rightElems[i] = new Element(elems[i].unit, -elems[i].pow, elems[i].root);
}
} else
rightElems = new Element[] { new Element(right, -1, 1) };
return getInstance(leftElems, rightElems);
}
/**
* Returns the product unit corresponding to the specified root of the specified unit.
*
* @param unit
* the unit.
* @param n
* the root's order (n > 0).
* @return unit^(1/nn)
* @throws ArithmeticException
* if n == 0
.
*/
public static AbstractUnit getRootInstance(Unit unit, int n) {
Element[] unitElems;
if (unit instanceof ProductUnit) {
Element[] elems = ((ProductUnit) unit).elements;
unitElems = new Element[elems.length];
for (int i = 0; i < elems.length; i++) {
int gcd = gcd(Math.abs(elems[i].pow), elems[i].root * n);
unitElems[i] = new Element(elems[i].unit, elems[i].pow / gcd, elems[i].root * n / gcd);
}
} else
unitElems = new Element[] { new Element(unit, 1, n) };
return getInstance(unitElems, new Element[0]);
}
/**
* Returns the product unit corresponding to this unit raised to the specified exponent.
*
* @param unit
* the unit.
* @param nn
* the exponent (nn > 0).
* @return unit^n
*/
public static AbstractUnit getPowInstance(Unit unit, int n) {
Element[] unitElems;
if (unit instanceof ProductUnit) {
Element[] elems = ((ProductUnit) unit).elements;
unitElems = new Element[elems.length];
for (int i = 0; i < elems.length; i++) {
int gcd = gcd(Math.abs(elems[i].pow * n), elems[i].root);
unitElems[i] = new Element(elems[i].unit, elems[i].pow * n / gcd, elems[i].root / gcd);
}
} else
unitElems = new Element[] { new Element(unit, n, 1) };
return getInstance(unitElems, new Element[0]);
}
/**
* Returns the number of unit elements in this product.
*
* @return the number of unit elements.
*/
public int getUnitCount() {
return elements.length;
}
/**
* Returns the unit element at the specified position.
*
* @param index
* the index of the unit element to return.
* @return the unit element at the specified position.
* @throws IndexOutOfBoundsException
* if index is out of range (index < 0 || index >= getUnitCount())
.
*/
public Unit getUnit(int index) {
return elements[index].getUnit();
}
/**
* Returns the power exponent of the unit element at the specified position.
*
* @param index
* the index of the unit element.
* @return the unit power exponent at the specified position.
* @throws IndexOutOfBoundsException
* if index is out of range (index < 0 || index >= getUnitCount())
.
*/
public int getUnitPow(int index) {
return elements[index].getPow();
}
/**
* Returns the root exponent of the unit element at the specified position.
*
* @param index
* the index of the unit element.
* @return the unit root exponent at the specified position.
* @throws IndexOutOfBoundsException
* if index is out of range (index < 0 || index >= getUnitCount())
.
*/
public int getUnitRoot(int index) {
return elements[index].getRoot();
}
@Override
public Map, Integer> getBaseUnits() {
final Map, Integer> units = new HashMap<>(); // Diamond (Java7+)
for (int i = 0; i < getUnitCount(); i++) {
units.put(getUnit(i), getUnitPow(i));
}
return units;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof ProductUnit) {
ProductUnit other = (ProductUnit) obj;
Arrays.sort(elements);
Arrays.sort(other.elements);
return Arrays.equals(elements, other.elements);
}
if (obj instanceof Unit) {
// A wrapper ProductUnit is equal to the unit it wraps
return elements.length == 1 && elements[0].pow == elements[0].root && obj.equals(elements[0].unit);
}
return false;
}
@Override
public int hashCode() {
if (elements.length == 1 && equals(elements[0])) {
return elements[0].hashCode();
}
Arrays.sort(elements);
return Arrays.hashCode(elements);
}
@SuppressWarnings("unchecked")
@Override
public AbstractUnit toSystemUnit() {
Unit systemUnit = AbstractUnit.ONE;
for (Element element : elements) {
Unit unit = element.unit.getSystemUnit();
unit = unit.pow(element.pow);
unit = unit.root(element.root);
systemUnit = systemUnit.multiply(unit);
}
return (AbstractUnit) systemUnit;
}
@Override
public boolean isSystemUnit() {
for (Element element : elements) {
if (!(element.unit instanceof AbstractUnit && ((AbstractUnit) element.unit).isSystemUnit())) {
return super.isSystemUnit();
}
}
return true;
}
@Override
public UnitConverter getSystemConverter() {
UnitConverter converter = AbstractConverter.IDENTITY;
for (Element e : elements) {
if (e.unit instanceof AbstractUnit) {
UnitConverter cvtr = ((AbstractUnit) e.unit).getSystemConverter();
if (!(cvtr.isLinear()))
throw new UnsupportedOperationException(e.unit + " is non-linear, cannot convert");
if (e.root != 1)
throw new UnsupportedOperationException(e.unit + " holds a base unit with fractional exponent");
int pow = e.pow;
if (pow < 0) { // Negative power.
pow = -pow;
cvtr = cvtr.inverse();
}
for (int j = 0; j < pow; j++) {
converter = converter.concatenate(cvtr);
}
}
}
return converter;
}
@Override
public Dimension getDimension() {
Dimension dimension = QuantityDimension.NONE;
for (int i = 0; i < this.getUnitCount(); i++) {
Unit unit = this.getUnit(i);
if (this.elements != null && unit.getDimension() != null) {
Dimension d = unit.getDimension().pow(this.getUnitPow(i)).root(this.getUnitRoot(i));
dimension = dimension.multiply(d);
}
}
return dimension;
}
/**
* Returns the unit defined from the product of the specified elements.
*
* @param leftElems
* left multiplicand elements.
* @param rightElems
* right multiplicand elements.
* @return the corresponding unit.
*/
@SuppressWarnings("rawtypes")
private static AbstractUnit getInstance(Element[] leftElems, Element[] rightElems) {
// Merges left elements with right elements.
Element[] result = new Element[leftElems.length + rightElems.length];
int resultIndex = 0;
for (Element leftElem : leftElems) {
Unit unit = leftElem.unit;
int p1 = leftElem.pow;
int r1 = leftElem.root;
int p2 = 0;
int r2 = 1;
for (Element rightElem : rightElems) {
if (unit.equals(rightElem.unit)) {
p2 = rightElem.pow;
r2 = rightElem.root;
break; // No duplicate.
}
}
int pow = (p1 * r2) + (p2 * r1);
int root = r1 * r2;
if (pow != 0) {
int gcd = gcd(Math.abs(pow), root);
result[resultIndex++] = new Element(unit, pow / gcd, root / gcd);
}
}
// Appends remaining right elements not merged.
for (Element rightElem : rightElems) {
Unit unit = rightElem.unit;
boolean hasBeenMerged = false;
for (Element leftElem : leftElems) {
if (unit.equals(leftElem.unit)) {
hasBeenMerged = true;
break;
}
}
if (!hasBeenMerged)
result[resultIndex++] = rightElem;
}
// Returns or creates instance.
if (resultIndex == 0)
return AbstractUnit.ONE;
else if ((resultIndex == 1) && (result[0].pow == result[0].root))
return maybeWrap(result[0].unit);
else {
Element[] elems = new Element[resultIndex];
System.arraycopy(result, 0, elems, 0, resultIndex);
return new ProductUnit(elems);
}
}
/**
* Wraps the given unit in a ProductUnit if it's not already an AbstractUnit.
*/
private static > AbstractUnit maybeWrap(Unit unit) {
if (unit instanceof AbstractUnit) {
return (AbstractUnit) unit;
} else {
return new ProductUnit(unit);
}
}
/**
* Returns the greatest common divisor (Euclid's algorithm).
*
* @param m
* the first number.
* @param nn
* the second number.
* @return the greatest common divisor.
*/
private static int gcd(int m, int n) {
if (n == 0)
return m;
else
return gcd(n, m % n);
}
/**
* Inner product element represents a rational power of a single unit.
*/
private final static class Element implements Serializable, Comparable {
/**
*
*/
private static final long serialVersionUID = 452938412398890507L;
/**
* Holds the single unit.
*/
private final Unit unit;
/**
* Holds the power exponent.
*/
private final int pow;
/**
* Holds the root exponent.
*/
private final int root;
/**
* Structural constructor.
*
* @param unit
* the unit.
* @param pow
* the power exponent.
* @param root
* the root exponent.
*/
private Element(Unit unit, int pow, int root) {
this.unit = unit;
this.pow = pow;
this.root = root;
}
/**
* Returns this element's unit.
*
* @return the single unit.
*/
public Unit getUnit() {
return unit;
}
/**
* Returns the power exponent. The power exponent can be negative but is always different from zero.
*
* @return the power exponent of the single unit.
*/
public int getPow() {
return pow;
}
/**
* Returns the root exponent. The root exponent is always greater than zero.
*
* @return the root exponent of the single unit.
*/
public int getRoot() {
return root;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Element element = (Element) o;
if (pow != element.pow) {
return false;
}
return root == element.root && (unit != null ? unit.equals(element.unit) : element.unit == null);
}
/**
* Arbitrary ordering, to be used only in sorting arrays for {@link ProductUnit#equals} and {@link ProductUnit#hashCode}.
*/
@Override
public int compareTo(Element other) {
// hashCode() - other.hashCode() may overflow
long ourHash = hashCode();
long theirHash = other.hashCode();
if (ourHash < theirHash) {
return -1;
} else if (ourHash == theirHash) {
return 0;
} else {
return 1;
}
}
@Override
public int hashCode() {
return Objects.hash(unit, ((double) pow) / root);
}
}
@Override
public String getSymbol() {
return symbol;
}
}