001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2020, Jean-Marie Dautelle, Werner Keil, Otavio Santana. 004 * 005 * All rights reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without modification, 008 * are permitted provided that the following conditions are met: 009 * 010 * 1. Redistributions of source code must retain the above copyright notice, 011 * this list of conditions and the following disclaimer. 012 * 013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 014 * and the following disclaimer in the documentation and/or other materials provided with the distribution. 015 * 016 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products 017 * derived from this software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package tech.units.indriya.unit; 031 032import java.io.Serializable; 033import java.util.Arrays; 034import java.util.LinkedHashMap; 035import java.util.Map; 036import java.util.Objects; 037 038import javax.measure.Dimension; 039import javax.measure.Quantity; 040import javax.measure.Unit; 041import javax.measure.UnitConverter; 042 043import tech.units.indriya.AbstractUnit; 044import tech.units.indriya.function.AbstractConverter; 045import tech.units.indriya.internal.function.Lazy; 046 047/** 048 * <p> 049 * This class represents units formed by the product of rational powers of existing physical units. 050 * </p> 051 * 052 * <p> 053 * This class maintains the canonical form of this product (simplest form after factorization). For example: <code>METRE.pow(2).divide(METRE)</code> 054 * returns <code>METRE</code>. 055 * </p> 056 * 057 * @param <Q> 058 * The type of the quantity measured by this unit. 059 * 060 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 061 * @author <a href="mailto:werner@units.tech">Werner Keil</a> 062 * @author Andi Huber 063 * @version 1.10, April 22, 2020 064 * @since 1.0 065 */ 066public final class ProductUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> { 067 068 /** 069 * 070 */ 071 private static final long serialVersionUID = 962983585531030093L; 072 073 /** 074 * Holds the units composing this product unit. 075 * 076 * @implNote considered immutable after constructor was called 077 */ 078 private final Element[] elements; 079 080 /** 081 * DefaultQuantityFactory constructor (used solely to create <code>ONE</code> instance). 082 */ 083 public ProductUnit() { 084 super(""); 085 elements = new Element[0]; 086 } 087 088 /** 089 * Copy constructor (allows for parameterization of product units). 090 * 091 * @param productUnit 092 * the product unit source. 093 * @throws ClassCastException 094 * if the specified unit is not a product unit. 095 */ 096 public ProductUnit(Unit<?> productUnit) { 097 super(productUnit.getSymbol()); 098 this.elements = ((ProductUnit<?>) productUnit).elements; 099 } 100 101 /** 102 * Product unit constructor. 103 * 104 * @param elements 105 * the product elements. 106 */ 107 private ProductUnit(Element[] elements) { 108 super(null); 109 this.elements = elements; 110 } 111 112 /** 113 * Returns the product of the specified units. 114 * 115 * @param left 116 * the left unit operand. 117 * @param right 118 * the right unit operand. 119 * @return <code>left * right</code> 120 */ 121 public static Unit<?> ofProduct(Unit<?> left, Unit<?> right) { 122 Element[] leftElems; 123 if (left instanceof ProductUnit<?>) { 124 leftElems = ((ProductUnit<?>) left).elements; 125 } else { 126 leftElems = new Element[] { new Element(left, 1, 1) }; 127 } 128 Element[] rightElems; 129 if (right instanceof ProductUnit<?>) { 130 rightElems = ((ProductUnit<?>) right).elements; 131 } else { 132 rightElems = new Element[] { new Element(right, 1, 1) }; 133 } 134 return getInstance(leftElems, rightElems); 135 } 136 137 /** 138 * Returns the quotient of the specified units. 139 * 140 * @param left 141 * the dividend unit operand. 142 * @param right 143 * the divisor unit operand. 144 * @return <code>dividend / divisor</code> 145 */ 146 public static Unit<?> ofQuotient(Unit<?> left, Unit<?> right) { 147 Element[] leftElems; 148 if (left instanceof ProductUnit<?>) 149 leftElems = ((ProductUnit<?>) left).elements; 150 else 151 leftElems = new Element[] { new Element(left, 1, 1) }; 152 Element[] rightElems; 153 if (right instanceof ProductUnit<?>) { 154 Element[] elems = ((ProductUnit<?>) right).elements; 155 rightElems = new Element[elems.length]; 156 for (int i = 0; i < elems.length; i++) { 157 rightElems[i] = new Element(elems[i].unit, -elems[i].pow, elems[i].root); 158 } 159 } else 160 rightElems = new Element[] { new Element(right, -1, 1) }; 161 return getInstance(leftElems, rightElems); 162 } 163 164 /** 165 * Returns the product unit corresponding to the specified root of the specified unit. 166 * 167 * @param unit 168 * the unit. 169 * @param n 170 * the root's order (n > 0). 171 * @return <code>unit^(1/nn)</code> 172 * @throws ArithmeticException 173 * if <code>n == 0</code>. 174 */ 175 public static Unit<?> ofRoot(Unit<?> unit, int n) { 176 Element[] unitElems; 177 if (unit instanceof ProductUnit<?>) { 178 Element[] elems = ((ProductUnit<?>) unit).elements; 179 unitElems = new Element[elems.length]; 180 for (int i = 0; i < elems.length; i++) { 181 int gcd = gcd(Math.abs(elems[i].pow), elems[i].root * n); 182 unitElems[i] = new Element(elems[i].unit, elems[i].pow / gcd, elems[i].root * n / gcd); 183 } 184 } else 185 unitElems = new Element[] { new Element(unit, 1, n) }; 186 return getInstance(unitElems, new Element[0]); 187 } 188 189 /** 190 * Returns the product unit corresponding to this unit raised to the specified exponent. 191 * 192 * @param unit 193 * the unit. 194 * @param nn 195 * the exponent (nn > 0). 196 * @return <code>unit^n</code> 197 */ 198 public static Unit<?> ofPow(Unit<?> unit, int n) { 199 Element[] unitElems; 200 if (unit instanceof ProductUnit<?>) { 201 Element[] elems = ((ProductUnit<?>) unit).elements; 202 unitElems = new Element[elems.length]; 203 for (int i = 0; i < elems.length; i++) { 204 int gcd = gcd(Math.abs(elems[i].pow * n), elems[i].root); 205 unitElems[i] = new Element(elems[i].unit, elems[i].pow * n / gcd, elems[i].root / gcd); 206 } 207 } else 208 unitElems = new Element[] { new Element(unit, n, 1) }; 209 return getInstance(unitElems, new Element[0]); 210 } 211 212 @Override 213 public Unit<?> pow(int n) { 214 return ofPow(this, n); 215 } 216 217 /** 218 * Returns the number of unit elements in this product. 219 * 220 * @return the number of unit elements. 221 */ 222 public int getUnitCount() { 223 return elements.length; 224 } 225 226 /** 227 * Returns the unit element at the specified position. 228 * 229 * @param index 230 * the index of the unit element to return. 231 * @return the unit element at the specified position. 232 * @throws IndexOutOfBoundsException 233 * if index is out of range <code>(index < 0 || index >= getUnitCount())</code>. 234 */ 235 public Unit<?> getUnit(int index) { 236 return elements[index].getUnit(); 237 } 238 239 /** 240 * Returns the power exponent of the unit element at the specified position. 241 * 242 * @param index 243 * the index of the unit element. 244 * @return the unit power exponent at the specified position. 245 * @throws IndexOutOfBoundsException 246 * if index is out of range <code>(index < 0 || index >= getUnitCount())</code>. 247 */ 248 public int getUnitPow(int index) { 249 return elements[index].getPow(); 250 } 251 252 /** 253 * Returns the root exponent of the unit element at the specified position. 254 * 255 * @param index 256 * the index of the unit element. 257 * @return the unit root exponent at the specified position. 258 * @throws IndexOutOfBoundsException 259 * if index is out of range <code>(index < 0 || index >= getUnitCount())</code>. 260 */ 261 public int getUnitRoot(int index) { 262 return elements[index].getRoot(); 263 } 264 265 @Override 266 public Map<Unit<?>, Integer> getBaseUnits() { 267 final Map<Unit<?>, Integer> units = new LinkedHashMap<>(); 268 for (int i = 0; i < getUnitCount(); i++) { 269 units.put(getUnit(i), getUnitPow(i)); 270 } 271 return units; 272 } 273 274 @Override 275 public boolean equals(Object obj) { 276 if (this == obj) { 277 return true; 278 } 279 if (obj instanceof ProductUnit<?>) { 280 final ProductUnit<?> other = ((ProductUnit<?>) obj); 281 return ElementUtil.arrayEqualsArbitraryOrder(this.elements, other.elements); 282 } 283 return false; 284 } 285 286 // thread safe cache for the expensive hashCode calculation 287 private transient Lazy<Integer> hashCode = new Lazy<>(this::calculateHashCode); 288 private int calculateHashCode() { 289 return Objects.hash((Object[]) ElementUtil.copyAndSort(elements)); 290 } 291 292 @Override 293 public int hashCode() { 294 return hashCode.get(); // lazy and thread-safe 295 } 296 297 @SuppressWarnings("unchecked") 298 @Override 299 public Unit<Q> toSystemUnit() { 300 Unit<?> systemUnit = AbstractUnit.ONE; 301 for (Element element : elements) { 302 Unit<?> unit = element.unit.getSystemUnit(); 303 unit = unit.pow(element.pow); 304 unit = unit.root(element.root); 305 systemUnit = systemUnit.multiply(unit); 306 } 307 return (AbstractUnit<Q>) systemUnit; 308 } 309 310 @Override 311 public UnitConverter getSystemConverter() { 312 UnitConverter converter = AbstractConverter.IDENTITY; 313 for (Element e : elements) { 314 if (e.unit instanceof AbstractUnit) { 315 UnitConverter cvtr = ((AbstractUnit<?>) e.unit).getSystemConverter(); 316 if (!(cvtr.isLinear())) 317 throw new UnsupportedOperationException(e.unit + " is non-linear, cannot convert"); 318 if (e.root != 1) 319 throw new UnsupportedOperationException(e.unit + " holds a base unit with fractional exponent"); 320 int pow = e.pow; 321 if (pow < 0) { // Negative power. 322 pow = -pow; 323 cvtr = cvtr.inverse(); 324 } 325 for (int j = 0; j < pow; j++) { 326 converter = converter.concatenate(cvtr); 327 } 328 } 329 } 330 return converter; 331 } 332 333 @Override 334 public Dimension getDimension() { 335 Dimension dimension = UnitDimension.NONE; 336 for (int i = 0; i < this.getUnitCount(); i++) { 337 Unit<?> unit = this.getUnit(i); 338 if (this.elements != null && unit.getDimension() != null) { 339 Dimension d = unit.getDimension().pow(this.getUnitPow(i)).root(this.getUnitRoot(i)); 340 dimension = dimension.multiply(d); 341 } 342 } 343 return dimension; 344 } 345 346 /** 347 * Returns the unit defined from the product of the specified elements. 348 * 349 * @param leftElems 350 * left multiplicand elements. 351 * @param rightElems 352 * right multiplicand elements. 353 * @return the corresponding unit. 354 */ 355 @SuppressWarnings("rawtypes") 356 private static Unit<?> getInstance(Element[] leftElems, Element[] rightElems) { 357 358 // Merges left elements with right elements. 359 Element[] result = new Element[leftElems.length + rightElems.length]; 360 int resultIndex = 0; 361 for (Element leftElem : leftElems) { 362 Unit<?> unit = leftElem.unit; 363 int p1 = leftElem.pow; 364 int r1 = leftElem.root; 365 int p2 = 0; 366 int r2 = 1; 367 for (Element rightElem : rightElems) { 368 if (unit.equals(rightElem.unit)) { 369 p2 = rightElem.pow; 370 r2 = rightElem.root; 371 break; // No duplicate. 372 } 373 } 374 int pow = p1 * r2 + p2 * r1; 375 int root = r1 * r2; 376 if (pow != 0) { 377 int gcd = gcd(Math.abs(pow), root); 378 result[resultIndex++] = new Element(unit, pow / gcd, root / gcd); 379 } 380 } 381 382 // Appends remaining right elements not merged. 383 for (Element rightElem : rightElems) { 384 Unit<?> unit = rightElem.unit; 385 boolean hasBeenMerged = false; 386 for (Element leftElem : leftElems) { 387 if (unit.equals(leftElem.unit)) { 388 hasBeenMerged = true; 389 break; 390 } 391 } 392 if (!hasBeenMerged) 393 result[resultIndex++] = rightElem; 394 } 395 396 // Returns or creates instance. 397 if (resultIndex == 0) 398 return AbstractUnit.ONE; 399 else if (resultIndex == 1 && result[0].pow == result[0].root) 400 return result[0].unit; 401 else { 402 Element[] elems = new Element[resultIndex]; 403 System.arraycopy(result, 0, elems, 0, resultIndex); 404 return new ProductUnit(elems); 405 } 406 } 407 408 /** 409 * Returns the greatest common divisor (Euclid's algorithm). 410 * 411 * @param m 412 * the first number. 413 * @param nn 414 * the second number. 415 * @return the greatest common divisor. 416 */ 417 private static int gcd(int m, int n) { 418 return n == 0 ? m : gcd(n, m % n); 419 } 420 421 /** 422 * Inner product element represents a rational power of a single unit. 423 */ 424 private final static class Element implements Serializable { 425 426 /** 427 * 428 */ 429 private static final long serialVersionUID = 452938412398890507L; 430 431 /** 432 * Holds the single unit. 433 */ 434 private final Unit<?> unit; 435 436 /** 437 * Holds the power exponent. 438 */ 439 private final int pow; 440 441 /** 442 * Holds the root exponent. 443 */ 444 private final int root; 445 446 /** 447 * Structural constructor. 448 * 449 * @param unit 450 * the unit. 451 * @param pow 452 * the power exponent. 453 * @param root 454 * the root exponent. 455 */ 456 private Element(Unit<?> unit, int pow, int root) { 457 this.unit = unit; 458 this.pow = pow; 459 this.root = root; 460 } 461 462 /** 463 * Returns this element's unit. 464 * 465 * @return the single unit. 466 */ 467 public Unit<?> getUnit() { 468 return unit; 469 } 470 471 /** 472 * Returns the power exponent. The power exponent can be negative but is always different from zero. 473 * 474 * @return the power exponent of the single unit. 475 */ 476 public int getPow() { 477 return pow; 478 } 479 480 /** 481 * Returns the root exponent. The root exponent is always greater than zero. 482 * 483 * @return the root exponent of the single unit. 484 */ 485 public int getRoot() { 486 return root; 487 } 488 489 @Override 490 public boolean equals(Object o) { 491 if (this == o) 492 return true; 493 if (o == null || getClass() != o.getClass()) 494 return false; 495 496 final Element other = (Element) o; 497 498 if (!Objects.equals(this.pow, other.pow)) { 499 return false; 500 } 501 if (!Objects.equals(this.root, other.root)) { 502 return false; 503 } 504 if (!Objects.equals(this.unit, other.unit)) { 505 return false; 506 } 507 return true; 508 509 } 510 511 @Override 512 public int hashCode() { 513 return Objects.hash(unit, pow, root); 514 } 515 } 516 517 // Element specific algorithms provided locally to this class 518 private final static class ElementUtil { 519 520 // -- returns a defensive sorted copy, unless size <= 1 521 private static Element[] copyAndSort(final Element[] elements) { 522 if (elements == null || elements.length <= 1) { 523 return elements; 524 } 525 final Element[] elementsSorted = Arrays.copyOf(elements, elements.length); 526 Arrays.sort(elementsSorted, ElementUtil::compare); 527 return elementsSorted; 528 } 529 530 private static int compare(final Element e0, final Element e1) { 531 final Unit<?> sysUnit0 = e0.getUnit().getSystemUnit(); 532 final Unit<?> sysUnit1 = e1.getUnit().getSystemUnit(); 533 final String symbol0 = sysUnit0.getSymbol(); 534 final String symbol1 = sysUnit1.getSymbol(); 535 536 if (symbol0 != null && symbol1 != null) { 537 return symbol0.compareTo(symbol1); 538 } else { 539 return sysUnit0.toString().compareTo(sysUnit1.toString()); 540 } 541 } 542 543 // optimized for the fact, that can only return true, if for each element in e0 there exist a single match in e1 544 private static boolean arrayEqualsArbitraryOrder(final Element[] e0, final Element[] e1) { 545 if (e0.length != e1.length) { 546 return false; 547 } 548 for (Element left : e0) { 549 boolean unitFound = false; 550 for (Element right : e1) { 551 if (left.unit.equals(right.unit)) { 552 if (left.pow != right.pow || left.root != right.root) { 553 return false; 554 } else { 555 unitFound = true; 556 break; 557 } 558 } 559 } 560 if (!unitFound) { 561 return false; 562 } 563 } 564 return true; 565 } 566 567 568 569 } 570}