001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2024, 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 static javax.measure.Quantity.Scale.RELATIVE; 033 034import java.io.InvalidObjectException; 035import java.io.ObjectInputStream; 036import java.io.Serializable; 037import java.util.Arrays; 038import java.util.LinkedHashMap; 039import java.util.Map; 040import java.util.Objects; 041 042import javax.measure.Dimension; 043import javax.measure.Quantity; 044import javax.measure.Unit; 045import javax.measure.UnitConverter; 046 047import tech.units.indriya.AbstractUnit; 048import tech.units.indriya.function.AbstractConverter; 049import tech.units.indriya.internal.function.Lazy; 050 051/** 052 * <p> 053 * This class represents units formed by the product of rational powers of existing physical units. 054 * </p> 055 * 056 * <p> 057 * This class maintains the canonical form of this product (simplest form after factorization). For example: <code>METRE.pow(2).divide(METRE)</code> 058 * returns <code>METRE</code>. 059 * </p> 060 * 061 * @param <Q> 062 * The type of the quantity measured by this unit. 063 * 064 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 065 * @author <a href="mailto:werner@units.tech">Werner Keil</a> 066 * @author Andi Huber 067 * @version 2.1, September 30, 2024 068 * @since 1.0 069 */ 070public final class ProductUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> { 071 072 /** 073 * 074 */ 075 private static final long serialVersionUID = 962983585531030093L; 076 077 /** 078 * Holds the units composing this product unit. 079 * 080 * <dl> 081 * <dt><span class="strong">Implementation Note:</span></dt><dd>considered immutable after constructor was called</dd> 082 * </dl> 083 */ 084 private final Element[] elements; 085 086 /** 087 * DefaultQuantityFactory constructor (used solely to create <code>ONE</code> instance). 088 */ 089 public ProductUnit() { 090 super(""); 091 elements = new Element[0]; 092 } 093 094 /** 095 * Copy constructor (allows for parameterization of product units). 096 * 097 * @param productUnit 098 * the product unit source. 099 * @throws ClassCastException 100 * if the specified unit is not a product unit. 101 */ 102 public ProductUnit(Unit<?> productUnit) { 103 super(productUnit.getSymbol()); 104 this.elements = ((ProductUnit<?>) productUnit).elements; 105 } 106 107 /** 108 * Product unit constructor. 109 * 110 * @param elements 111 * the product elements. 112 */ 113 private ProductUnit(Element[] elements) { 114 super(null); 115 this.elements = elements; 116 } 117 118 /** 119 * Returns the product of the specified units. 120 * 121 * @param left 122 * the left unit operand. 123 * @param right 124 * the right unit operand. 125 * @return <code>left * right</code> 126 */ 127 public static Unit<?> ofProduct(Unit<?> left, Unit<?> right) { 128 Element[] leftElems; 129 if (left instanceof ProductUnit<?>) { 130 leftElems = ((ProductUnit<?>) left).elements; 131 } else { 132 leftElems = new Element[] { new Element(left, 1, 1) }; 133 } 134 Element[] rightElems; 135 if (right instanceof ProductUnit<?>) { 136 rightElems = ((ProductUnit<?>) right).elements; 137 } else { 138 rightElems = new Element[] { new Element(right, 1, 1) }; 139 } 140 return getInstance(leftElems, rightElems); 141 } 142 143 /** 144 * Returns the quotient of the specified units. 145 * 146 * @param left 147 * the dividend unit operand. 148 * @param right 149 * the divisor unit operand. 150 * @return <code>dividend / divisor</code> 151 */ 152 public static Unit<?> ofQuotient(Unit<?> left, Unit<?> right) { 153 Element[] leftElems; 154 if (left instanceof ProductUnit<?>) 155 leftElems = ((ProductUnit<?>) left).elements; 156 else 157 leftElems = new Element[] { new Element(left, 1, 1) }; 158 Element[] rightElems; 159 if (right instanceof ProductUnit<?>) { 160 Element[] elems = ((ProductUnit<?>) right).elements; 161 rightElems = new Element[elems.length]; 162 for (int i = 0; i < elems.length; i++) { 163 rightElems[i] = new Element(elems[i].unit, -elems[i].pow, elems[i].root); 164 } 165 } else 166 rightElems = new Element[] { new Element(right, -1, 1) }; 167 return getInstance(leftElems, rightElems); 168 } 169 170 /** 171 * Returns the product unit corresponding to the specified root of the specified unit. 172 * 173 * @param unit 174 * the unit. 175 * @param n 176 * the root's order (n > 0). 177 * @return <code>unit^(1/nn)</code> 178 * @throws ArithmeticException 179 * if <code>n == 0</code>. 180 */ 181 public static Unit<?> ofRoot(Unit<?> unit, int n) { 182 Element[] unitElems; 183 if (unit instanceof ProductUnit<?>) { 184 Element[] elems = ((ProductUnit<?>) unit).elements; 185 unitElems = new Element[elems.length]; 186 for (int i = 0; i < elems.length; i++) { 187 int gcd = gcd(Math.abs(elems[i].pow), elems[i].root * n); 188 unitElems[i] = new Element(elems[i].unit, elems[i].pow / gcd, elems[i].root * n / gcd); 189 } 190 } else 191 unitElems = new Element[] { new Element(unit, 1, n) }; 192 return getInstance(unitElems, new Element[0]); 193 } 194 195 /** 196 * Returns the product unit corresponding to this unit raised to the specified exponent. 197 * 198 * @param unit 199 * the unit. 200 * @param nn 201 * the exponent (nn > 0). 202 * @return <code>unit^n</code> 203 */ 204 public static Unit<?> ofPow(Unit<?> unit, int n) { 205 Element[] unitElems; 206 if (unit instanceof ProductUnit<?>) { 207 Element[] elems = ((ProductUnit<?>) unit).elements; 208 unitElems = new Element[elems.length]; 209 for (int i = 0; i < elems.length; i++) { 210 int gcd = gcd(Math.abs(elems[i].pow * n), elems[i].root); 211 unitElems[i] = new Element(elems[i].unit, elems[i].pow * n / gcd, elems[i].root / gcd); 212 } 213 } else 214 unitElems = new Element[] { new Element(unit, n, 1) }; 215 return getInstance(unitElems, new Element[0]); 216 } 217 218 @Override 219 public Unit<?> pow(int n) { 220 return ofPow(this, n); 221 } 222 223 /** 224 * Returns the number of unit elements in this product. 225 * 226 * @return the number of unit elements. 227 */ 228 public int getUnitCount() { 229 return elements.length; 230 } 231 232 /** 233 * Returns the unit element at the specified position. 234 * 235 * @param index 236 * the index of the unit element to return. 237 * @return the unit element at the specified position. 238 * @throws IndexOutOfBoundsException 239 * if index is out of range <code>(index < 0 || index >= getUnitCount())</code>. 240 */ 241 public Unit<?> getUnit(int index) { 242 return elements[index].getUnit(); 243 } 244 245 /** 246 * Returns the power exponent of the unit element at the specified position. 247 * 248 * @param index 249 * the index of the unit element. 250 * @return the unit power exponent at the specified position. 251 * @throws IndexOutOfBoundsException 252 * if index is out of range <code>(index < 0 || index >= getUnitCount())</code>. 253 */ 254 public int getUnitPow(int index) { 255 return elements[index].getPow(); 256 } 257 258 /** 259 * Returns the root exponent of the unit element at the specified position. 260 * 261 * @param index 262 * the index of the unit element. 263 * @return the unit root exponent at the specified position. 264 * @throws IndexOutOfBoundsException 265 * if index is out of range <code>(index < 0 || index >= getUnitCount())</code>. 266 */ 267 public int getUnitRoot(int index) { 268 return elements[index].getRoot(); 269 } 270 271 @Override 272 public Map<Unit<?>, Integer> getBaseUnits() { 273 final Map<Unit<?>, Integer> units = new LinkedHashMap<>(); 274 for (int i = 0; i < getUnitCount(); i++) { 275 units.put(getUnit(i), getUnitPow(i)); 276 } 277 return units; 278 } 279 280 @Override 281 public boolean equals(Object obj) { 282 if (this == obj) { 283 return true; 284 } 285 if (obj instanceof ProductUnit<?>) { 286 final ProductUnit<?> other = ((ProductUnit<?>) obj); 287 return ElementUtil.arrayEqualsArbitraryOrder(this.elements, other.elements); 288 } 289 return false; 290 } 291 292 // thread safe cache for the expensive hashCode calculation 293 private transient Lazy<Integer> hashCode = new Lazy<>(this::calculateHashCode); 294 private int calculateHashCode() { 295 return Objects.hash((Object[]) ElementUtil.copyAndSort(elements)); 296 } 297 298 @Override 299 public int hashCode() { 300 return hashCode.get(); // lazy and thread-safe 301 } 302 303 @SuppressWarnings("unchecked") 304 @Override 305 public Unit<Q> toSystemUnit() { 306 Unit<?> systemUnit = AbstractUnit.ONE; 307 for (Element element : elements) { 308 Unit<?> unit = element.unit.getSystemUnit(); 309 unit = unit.pow(element.pow); 310 unit = unit.root(element.root); 311 systemUnit = systemUnit.multiply(unit); 312 } 313 return (AbstractUnit<Q>) systemUnit; 314 } 315 316 @Override 317 public UnitConverter getSystemConverter() { 318 UnitConverter converter = AbstractConverter.IDENTITY; 319 for (Element e : elements) { 320 if (e.unit instanceof AbstractUnit) { 321 UnitConverter cvtr = ((AbstractUnit<?>) e.unit).getSystemConverter(); 322 if (!RELATIVE.equals(scale) && !(cvtr.isLinear())) 323 throw new UnsupportedOperationException("cannot compose " + e.unit + " into a product converter, " 324 + "because " + e.unit + " holds a system converter " 325 + "that is NOT a linear transformation (a linear function without offset)"); 326 if (e.root != 1) 327 throw new UnsupportedOperationException("cannot compose " + e.unit + " into a product converter, " 328 + "because " + e.unit + " holds a base unit with exponent not equal to 1"); 329 int pow = e.pow; 330 if (pow < 0) { // Negative power. 331 pow = -pow; 332 cvtr = cvtr.inverse(); 333 } 334 for (int j = 0; j < pow; j++) { 335 converter = converter.concatenate(cvtr); 336 } 337 } 338 } 339 return converter; 340 } 341 342 @Override 343 public Dimension getDimension() { 344 Dimension dimension = UnitDimension.NONE; 345 for (int i = 0; i < this.getUnitCount(); i++) { 346 Unit<?> unit = this.getUnit(i); 347 if (this.elements != null && unit.getDimension() != null) { 348 Dimension d = unit.getDimension().pow(this.getUnitPow(i)).root(this.getUnitRoot(i)); 349 dimension = dimension.multiply(d); 350 } 351 } 352 return dimension; 353 } 354 355 /** 356 * Returns the unit defined from the product of the specified elements. 357 * 358 * @param leftElems 359 * left multiplicand elements. 360 * @param rightElems 361 * right multiplicand elements. 362 * @return the corresponding unit. 363 */ 364 @SuppressWarnings("rawtypes") 365 private static Unit<?> getInstance(Element[] leftElems, Element[] rightElems) { 366 367 // Merges left elements with right elements. 368 Element[] result = new Element[leftElems.length + rightElems.length]; 369 int resultIndex = 0; 370 for (Element leftElem : leftElems) { 371 Unit<?> unit = leftElem.unit; 372 int p1 = leftElem.pow; 373 int r1 = leftElem.root; 374 int p2 = 0; 375 int r2 = 1; 376 for (Element rightElem : rightElems) { 377 if (unit.equals(rightElem.unit)) { 378 p2 = rightElem.pow; 379 r2 = rightElem.root; 380 break; // No duplicate. 381 } 382 } 383 int pow = p1 * r2 + p2 * r1; 384 int root = r1 * r2; 385 if (pow != 0) { 386 int gcd = gcd(Math.abs(pow), root); 387 result[resultIndex++] = new Element(unit, pow / gcd, root / gcd); 388 } 389 } 390 391 // Appends remaining right elements not merged. 392 for (Element rightElem : rightElems) { 393 Unit<?> unit = rightElem.unit; 394 boolean hasBeenMerged = false; 395 for (Element leftElem : leftElems) { 396 if (unit.equals(leftElem.unit)) { 397 hasBeenMerged = true; 398 break; 399 } 400 } 401 if (!hasBeenMerged) 402 result[resultIndex++] = rightElem; 403 } 404 405 // Returns or creates instance. 406 if (resultIndex == 0) 407 return AbstractUnit.ONE; 408 else if (resultIndex == 1 && result[0].pow == result[0].root) 409 return result[0].unit; 410 else { 411 Element[] elems = new Element[resultIndex]; 412 System.arraycopy(result, 0, elems, 0, resultIndex); 413 return new ProductUnit(elems); 414 } 415 } 416 417 /** 418 * Returns the greatest common divisor (Euclid's algorithm). 419 * 420 * @param m 421 * the first number. 422 * @param nn 423 * the second number. 424 * @return the greatest common divisor. 425 */ 426 private static int gcd(int m, int n) { 427 return n == 0 ? m : gcd(n, m % n); 428 } 429 430 /** 431 * Inner product element represents a rational power of a single unit. 432 */ 433 private final static class Element implements Serializable { 434 435 /** 436 * 437 */ 438 private static final long serialVersionUID = 452938412398890507L; 439 440 /** 441 * Holds the single unit. 442 */ 443 private final Unit<?> unit; 444 445 /** 446 * Holds the power exponent. 447 */ 448 private final int pow; 449 450 /** 451 * Holds the root exponent. 452 */ 453 private final int root; 454 455 /** 456 * Structural constructor. 457 * 458 * @param unit 459 * the unit. 460 * @param pow 461 * the power exponent. 462 * @param root 463 * the root exponent. 464 */ 465 private Element(Unit<?> unit, int pow, int root) { 466 this.unit = unit; 467 this.pow = pow; 468 this.root = root; 469 } 470 471 /** 472 * Returns this element's unit. 473 * 474 * @return the single unit. 475 */ 476 public Unit<?> getUnit() { 477 return unit; 478 } 479 480 /** 481 * Returns the power exponent. The power exponent can be negative but is always different from zero. 482 * 483 * @return the power exponent of the single unit. 484 */ 485 public int getPow() { 486 return pow; 487 } 488 489 /** 490 * Returns the root exponent. The root exponent is always greater than zero. 491 * 492 * @return the root exponent of the single unit. 493 */ 494 public int getRoot() { 495 return root; 496 } 497 498 @Override 499 public boolean equals(Object o) { 500 if (this == o) 501 return true; 502 if (o == null || getClass() != o.getClass()) 503 return false; 504 505 final Element other = (Element) o; 506 507 if (!Objects.equals(this.pow, other.pow)) { 508 return false; 509 } 510 if (!Objects.equals(this.root, other.root)) { 511 return false; 512 } 513 return Objects.equals(this.unit, other.unit); 514 } 515 516 @Override 517 public int hashCode() { 518 return Objects.hash(unit, pow, root); 519 } 520 } 521 522 // Element specific algorithms provided locally to this class 523 private final static class ElementUtil { 524 525 // -- returns a defensive sorted copy, unless size <= 1 526 private static Element[] copyAndSort(final Element[] elements) { 527 if (elements == null || elements.length <= 1) { 528 return elements; 529 } 530 final Element[] elementsSorted = Arrays.copyOf(elements, elements.length); 531 Arrays.sort(elementsSorted, ElementUtil::compare); 532 return elementsSorted; 533 } 534 535 private static int compare(final Element e0, final Element e1) { 536 final Unit<?> sysUnit0 = e0.getUnit().getSystemUnit(); 537 final Unit<?> sysUnit1 = e1.getUnit().getSystemUnit(); 538 final String symbol0 = sysUnit0.getSymbol(); 539 final String symbol1 = sysUnit1.getSymbol(); 540 541 if (symbol0 != null && symbol1 != null) { 542 return symbol0.compareTo(symbol1); 543 } else { 544 return sysUnit0.toString().compareTo(sysUnit1.toString()); 545 } 546 } 547 548 // optimized for the fact, that can only return true, if for each element in e0 there exist a single match in e1 549 private static boolean arrayEqualsArbitraryOrder(final Element[] e0, final Element[] e1) { 550 if (e0.length != e1.length) { 551 return false; 552 } 553 for (Element left : e0) { 554 boolean unitFound = false; 555 for (Element right : e1) { 556 if (left.unit.equals(right.unit)) { 557 if (left.pow != right.pow || left.root != right.root) { 558 return false; 559 } else { 560 unitFound = true; 561 break; 562 } 563 } 564 } 565 if (!unitFound) { 566 return false; 567 } 568 } 569 return true; 570 } 571 } 572 573 574 // -- SERIALIZATION PROXY 575 576 // Chapter 12. Serialization 577 // Bloch, Joshua. Effective Java (p. 339). Pearson Education. 578 579 private Object writeReplace() { 580 return new SerializationProxy(this); 581 } 582 583 private void readObject(ObjectInputStream stream) throws InvalidObjectException { 584 throw new InvalidObjectException("Proxy required"); 585 } 586 587 private static class SerializationProxy implements Serializable { 588 private static final long serialVersionUID = 1L; 589 private final Element[] elements; 590 private final String symbol; 591 592 private SerializationProxy(ProductUnit<?> productUnit) { 593 this.elements = productUnit.elements; 594 this.symbol = productUnit.getSymbol(); 595 } 596 597 private Object readResolve() { 598 ProductUnit<?> pu = new ProductUnit<>(elements); 599 pu.setSymbol(symbol); 600 return pu; 601 } 602 } 603 604}