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; 031 032import static javax.measure.Quantity.Scale.ABSOLUTE; 033import static org.apiguardian.api.API.Status.EXPERIMENTAL; 034 035import java.io.Serializable; 036import java.lang.reflect.ParameterizedType; 037import java.lang.reflect.Type; 038import java.util.HashMap; 039import java.util.Map; 040 041import javax.measure.Dimension; 042import javax.measure.IncommensurableException; 043import javax.measure.Prefix; 044import javax.measure.Quantity; 045import javax.measure.UnconvertibleException; 046import javax.measure.Unit; 047import javax.measure.UnitConverter; 048import javax.measure.Quantity.Scale; 049import javax.measure.format.MeasurementParseException; 050import javax.measure.quantity.Dimensionless; 051 052import org.apiguardian.api.API; 053 054import tech.units.indriya.format.LocalUnitFormat; 055import tech.units.indriya.format.SimpleUnitFormat; 056import tech.units.indriya.function.AbstractConverter; 057import tech.units.indriya.function.AddConverter; 058import tech.units.indriya.function.Calculus; 059import tech.units.indriya.function.MultiplyConverter; 060import tech.units.indriya.function.RationalNumber; 061import tech.units.indriya.internal.function.Calculator; 062import tech.units.indriya.spi.DimensionalModel; 063import tech.units.indriya.unit.AlternateUnit; 064import tech.units.indriya.unit.AnnotatedUnit; 065import tech.units.indriya.unit.ProductUnit; 066import tech.units.indriya.unit.TransformedUnit; 067import tech.units.indriya.unit.UnitDimension; 068import tech.units.indriya.unit.Units; 069import tech.uom.lib.common.function.Nameable; 070import tech.uom.lib.common.function.PrefixOperator; 071import tech.uom.lib.common.function.SymbolSupplier; 072 073/** 074 * <p> 075 * The class represents units founded on the seven <b>SI</b> base units for 076 * seven base quantities assumed to be mutually independent. 077 * </p> 078 * 079 * <p> 080 * For all physics units, unit conversions are symmetrical: 081 * <code>u1.getConverterTo(u2).equals(u2.getConverterTo(u1).inverse())</code>. 082 * Non-physical units (e.g. currency units) for which conversion is not 083 * symmetrical should have their own separate class hierarchy and are considered 084 * distinct (e.g. financial units), although they can always be combined with 085 * physics units (e.g. "€/Kg", "$/h"). 086 * </p> 087 * 088 * @see <a href= 089 * "http://en.wikipedia.org/wiki/International_System_of_Units">Wikipedia: 090 * International System of Units</a> 091 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 092 * @author <a href="mailto:werner@units.tech">Werner Keil</a> 093 * @version 4.1, October 4, 2024 094 * @since 1.0 095 */ 096public abstract class AbstractUnit<Q extends Quantity<Q>> 097 implements Unit<Q>, Comparable<Unit<Q>>, PrefixOperator<Q>, Nameable, Serializable, SymbolSupplier { 098 099 /** 100 * 101 */ 102 private static final long serialVersionUID = -4344589505537030204L; 103 104 /** 105 * Holds the dimensionless unit <code>ONE</code>. 106 * 107 * @see <a href= 108 * "https://en.wikipedia.org/wiki/Natural_units#Choosing_constants_to_normalize"> 109 * Wikipedia: Natural Units - Choosing constants to normalize</a> 110 * @see <a href= "http://www.av8n.com/physics/dimensionless-units.htm">Units of 111 * Dimension One</a> 112 */ 113 public static final Unit<Dimensionless> ONE = new ProductUnit<>(); 114 115 /** 116 * Holds the name. 117 */ 118 private String name; 119 120 /** 121 * Holds the symbol. 122 */ 123 private String symbol; 124 125 /** 126 * Holds the measurement scale 127 */ 128 protected Scale scale = ABSOLUTE; 129 130 /** 131 * Holds the unique symbols collection (base units or alternate units). 132 */ 133 protected static final transient Map<String, Unit<?>> SYMBOL_TO_UNIT = new HashMap<>(); 134 135 /** 136 * Default constructor. 137 */ 138 protected AbstractUnit() { 139 } 140 141 /** 142 * Constructor setting a symbol. 143 * 144 * @param symbol the unit symbol. 145 */ 146 protected AbstractUnit(String symbol) { 147 this.symbol = symbol; 148 } 149 150 protected Type getActualType() { 151 ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass(); 152 return parameterizedType.getActualTypeArguments()[0].getClass().getGenericInterfaces()[0]; 153 } 154 155 /** 156 * Indicates if this unit belongs to the set of coherent SI units (unscaled SI 157 * units). 158 * 159 * The base and coherent derived units of the SI form a coherent set, designated 160 * the set of coherent SI units. The word coherent is used here in the following 161 * sense: when coherent units are used, equations between the numerical values 162 * of quantities take exactly the same form as the equations between the 163 * quantities themselves. Thus if only units from a coherent set are used, 164 * conversion factors between units are never required. 165 * 166 * @return <code>equals(toSystemUnit())</code> 167 */ 168 public boolean isSystemUnit() { 169 Unit<Q> sys = this.toSystemUnit(); 170 return this == sys || this.equals(sys); 171 } 172 173 /** 174 * Returns the converter from this unit to its unscaled {@link #toSystemUnit 175 * System Unit} unit. 176 * 177 * @return <code>getConverterTo(this.toSystemUnit())</code> 178 * @see #toSystemUnit 179 */ 180 public abstract UnitConverter getSystemConverter(); 181 182 /** 183 * Returns the unscaled {@link SI} unit from which this unit is derived. 184 * 185 * The SI unit can be be used to identify a quantity given the unit. For 186 * example:<code> static boolean isAngularVelocity(AbstractUnit<?> unit) { 187 * return unit.toSystemUnit().equals(RADIAN.divide(SECOND)); } assert(REVOLUTION.divide(MINUTE).isAngularVelocity()); // Returns true. </code> 188 * 189 * @return the unscaled metric unit from which this unit is derived. 190 */ 191 protected abstract Unit<Q> toSystemUnit(); 192 193 /** 194 * Annotates the specified unit. Annotation does not change the unit semantic. 195 * Annotations are often written between curly braces behind units. For 196 * example:<br> 197 * <code> Unit<Volume> PERCENT_VOL = ((AbstractUnit)Units.PERCENT).annotate("vol"); // "%{vol}" Unit<Mass> KG_TOTAL = 198 * ((AbstractUnit)Units.KILOGRAM).annotate("total"); // "kg{total}" Unit<Dimensionless> RED_BLOOD_CELLS = ((AbstractUnit)Units.ONE).annotate("RBC"); // "{RBC}" </code> 199 * 200 * Note: Annotation of system units are not considered themselves as system 201 * units. 202 * 203 * @param annotation the unit annotation. 204 * @return the annotated unit. 205 */ 206 public final Unit<Q> annotate(String annotation) { 207 return new AnnotatedUnit<>(this, annotation); 208 } 209 210 /** 211 * Returns the abstract unit represented by the specified characters as per 212 * default format. 213 * 214 * Locale-sensitive unit parsing could be handled using {@link LocalUnitFormat} 215 * in subclasses of AbstractUnit. 216 * 217 * <p> 218 * Note: The standard format supports dimensionless 219 * units.<code> AbstractUnit<Dimensionless> PERCENT = 220 * AbstractUnit.parse("100").inverse().asType(Dimensionless.class); </code> 221 * </p> 222 * 223 * @param charSequence the character sequence to parse. 224 * @return <code>SimpleUnitFormat.getInstance().parse(csq)</code> 225 * @throws MeasurementParseException if the specified character sequence cannot 226 * be correctly parsed (e.g. not UCUM 227 * compliant). 228 */ 229 public static Unit<?> parse(CharSequence charSequence) { 230 return SimpleUnitFormat.getInstance().parse(charSequence); 231 } 232 233 /** 234 * Returns the standard representation of this physics unit. The string produced 235 * for a given unit is always the same; it is not affected by the locale. It can 236 * be used as a canonical string representation for exchanging units, or as a 237 * key for a Hashtable, etc. 238 * 239 * Locale-sensitive unit parsing could be handled using {@link LocalUnitFormat} 240 * in subclasses of AbstractUnit. 241 * 242 * @return <code>SimpleUnitFormat.getInstance().format(this)</code> 243 */ 244 @Override 245 public String toString() { 246 return SimpleUnitFormat.getInstance().format(this); 247 } 248 249 // /////////////////////////////////////////////////////// 250 // Implements javax.measure.Unit<Q> interface // 251 // /////////////////////////////////////////////////////// 252 253 /** 254 * Returns the system unit (unscaled SI unit) from which this unit is derived. 255 * They can be be used to identify a quantity given the unit. For example:<br> 256 * <code> static boolean isAngularVelocity(AbstractUnit<?> unit) {<br> return unit.getSystemUnit().equals(RADIAN.divide(SECOND));<br>} 257 * <br>assert(REVOLUTION.divide(MINUTE).isAngularVelocity()); // Returns true. </code> 258 * 259 * @return the unscaled metric unit from which this unit is derived. 260 */ 261 @Override 262 public final Unit<Q> getSystemUnit() { 263 return toSystemUnit(); 264 } 265 266 /** 267 * Indicates if this unit is compatible with the unit specified. To be 268 * compatible both units must be physics units having the same fundamental 269 * dimension. 270 * 271 * @param that the other unit. 272 * @return <code>true</code> if this unit and that unit have the same 273 * fundamental dimension according to the current dimensional model; 274 * <code>false</code> otherwise. 275 */ 276 @Override 277 public final boolean isCompatible(Unit<?> that) { 278 return internalIsCompatible(that, true); 279 } 280 281 /** 282 * Casts this unit to a parameterized unit of specified nature or throw a 283 * ClassCastException if the dimension of the specified quantity and this unit's 284 * dimension do not match (regardless whether or not the dimensions are 285 * independent or not). 286 * 287 * @param type the quantity class identifying the nature of the unit. 288 * @throws ClassCastException if the dimension of this unit is different from 289 * the SI dimension of the specified type. 290 * @see Units#getUnit(Class) 291 */ 292 @SuppressWarnings("unchecked") 293 @Override 294 public final <T extends Quantity<T>> Unit<T> asType(Class<T> type) { 295 Dimension typeDimension = UnitDimension.of(type); 296 if (typeDimension != null && !typeDimension.equals(this.getDimension())) 297 throw new ClassCastException("The unit: " + this + " is not compatible with quantities of type " + type); 298 return (Unit<T>) this; 299 } 300 301 @Override 302 public abstract Map<? extends Unit<?>, Integer> getBaseUnits(); 303 304 @Override 305 public abstract Dimension getDimension(); 306 307 protected void setName(String name) { 308 this.name = name; 309 } 310 311 public String getName() { 312 return name; 313 } 314 315 public String getSymbol() { 316 return symbol; 317 } 318 319 protected void setSymbol(String s) { 320 this.symbol = s; 321 } 322 323 @Override 324 public final UnitConverter getConverterTo(Unit<Q> that) throws UnconvertibleException { 325 return internalGetConverterTo(that, true); 326 } 327 328 @Override 329 public final UnitConverter getConverterToAny(Unit<?> that) throws IncommensurableException, UnconvertibleException { 330 return getConverterToAny(that, ABSOLUTE); 331 } 332 333 /** 334 * Returns a converter of numeric values from this unit to another unit of same type. This method performs the same work as 335 * {@link #getConverterToAny(Unit)} without raising checked exception. 336 * 337 * @param that 338 * the unit of same type to which to convert the numeric values. 339 * @param scale the measurement scale. 340 * @return the converter from this unit to {@code that} unit in the given {@code scale}. 341 * @throws UnconvertibleException 342 * if a converter cannot be constructed. 343 * 344 * @see #getConverterToAny(Unit) 345 */ 346 @API(status=EXPERIMENTAL) 347 public final UnitConverter getConverterTo(Unit<Q> that, Scale scale) throws UnconvertibleException { 348 this.scale = scale; 349 return getConverterTo(that); 350 } 351 352 /** 353 * Returns a converter from this unit to the specified unit of type unknown in the given scale. This method can be used when the quantity type of the specified unit is 354 * unknown at compile-time or when dimensional analysis allows for conversion between units of different type. 355 * 356 * <p> 357 * To convert to a unit having the same parameterized type, {@link #getConverterTo(Unit, Scale)} is preferred (no checked exception raised). 358 * </p> 359 * 360 * @param that 361 * the unit to which to convert the numeric values. 362 * @param scale the measurement scale. 363 * @return the converter from {@code this} unit to {@code that} unit using the given {@code scale}. 364 * @throws IncommensurableException 365 * if this unit is not {@linkplain #isCompatible(Unit) compatible} with {@code that}. 366 * @throws UnconvertibleException 367 * if a converter cannot be constructed. 368 * 369 * @see #getConverterTo(Unit) 370 * @see #isCompatible(Unit) 371 */ 372 @API(status=EXPERIMENTAL) 373 @SuppressWarnings("rawtypes") 374 public final UnitConverter getConverterToAny(Unit<?> that, Scale scale) throws IncommensurableException, UnconvertibleException { 375 if (!isCompatible(that)) 376 throw new IncommensurableException(this + " is not compatible with " + that); 377 this.scale = scale; 378 final AbstractUnit thatAbstr = (AbstractUnit) that; // Since both units are 379 // compatible they must both be abstract units. 380 final DimensionalModel model = DimensionalModel.current(); 381 Unit thisSystemUnit = this.getSystemUnit(); 382 UnitConverter thisToDimension = model.getDimensionalTransform(thisSystemUnit.getDimension()) 383 .concatenate(this.getSystemConverter()); 384 Unit thatSystemUnit = thatAbstr.getSystemUnit(); 385 UnitConverter thatToDimension = model.getDimensionalTransform(thatSystemUnit.getDimension()) 386 .concatenate(thatAbstr.getSystemConverter()); 387 return thatToDimension.inverse().concatenate(thisToDimension); 388 } 389 390 @Override 391 public final Unit<Q> alternate(String newSymbol) { 392 return new AlternateUnit<>(this, newSymbol); 393 } 394 395 @Override 396 public final Unit<Q> transform(UnitConverter operation) { 397 Unit<Q> systemUnit = this.getSystemUnit(); 398 UnitConverter cvtr; 399 if (this.isSystemUnit()) { 400 cvtr = this.getSystemConverter().concatenate(operation); 401 } else { 402 cvtr = operation; 403 } 404 return cvtr.isIdentity() ? systemUnit : new TransformedUnit<>(null, this, systemUnit, cvtr); 405 } 406 407 @Override 408 public final Unit<Q> shift(Number offset) { 409 if (Calculus.currentNumberSystem().isZero(offset)) 410 return this; 411 return transform(new AddConverter(offset)); 412 } 413 414 @Override 415 public final Unit<Q> multiply(Number factor) { 416 if (Calculus.currentNumberSystem().isOne(factor)) 417 return this; 418 return transform(MultiplyConverter.of(factor)); 419 } 420 421 @Override 422 public Unit<Q> shift(double offset) { 423 return shift(RationalNumber.of(offset)); 424 } 425 426 @Override 427 public Unit<Q> multiply(double multiplier) { 428 return multiply(RationalNumber.of(multiplier)); 429 } 430 431 @Override 432 public Unit<Q> divide(double divisor) { 433 return divide(RationalNumber.of(divisor)); 434 } 435 436 /** 437 * Internal helper for isCompatible 438 */ 439 private final boolean internalIsCompatible(Unit<?> that, boolean checkEquals) { 440 if (checkEquals) { 441 if (this == that || this.equals(that)) 442 return true; 443 } else { 444 if (this == that) 445 return true; 446 } 447 if (!(that instanceof Unit)) 448 return false; 449 Dimension thisDimension = this.getDimension(); 450 Dimension thatDimension = that.getDimension(); 451 if (thisDimension.equals(thatDimension)) 452 return true; 453 DimensionalModel model = DimensionalModel.current(); // Use 454 // dimensional 455 // analysis 456 // model. 457 return model.getFundamentalDimension(thisDimension).equals(model.getFundamentalDimension(thatDimension)); 458 } 459 460 protected final UnitConverter internalGetConverterTo(Unit<Q> that, boolean useEquals) 461 throws UnconvertibleException { 462 if (useEquals) { 463 if (this == that || this.equals(that)) 464 return AbstractConverter.IDENTITY; 465 } else { 466 if (this == that) 467 return AbstractConverter.IDENTITY; 468 } 469 Unit<Q> thisSystemUnit = this.getSystemUnit(); 470 Unit<Q> thatSystemUnit = that.getSystemUnit(); 471 if (!thisSystemUnit.equals(thatSystemUnit)) 472 try { 473 return getConverterToAny(that); 474 } catch (IncommensurableException e) { 475 throw new UnconvertibleException(e); 476 } 477 UnitConverter thisToSI = this.getSystemConverter(); 478 UnitConverter thatToSI = that.getConverterTo(thatSystemUnit); 479 return thatToSI.inverse().concatenate(thisToSI); 480 } 481 482 /** 483 * Returns the product of this physical unit with the one specified. 484 * 485 * @param that the physical unit multiplicand. 486 * @return <code>this * that</code> 487 */ 488 public final Unit<?> multiply(Unit<?> that) { 489 if (this.equals(ONE)) 490 return that; 491 if (that.equals(ONE)) 492 return this; 493 return ProductUnit.ofProduct(this, that); 494 } 495 496 /** 497 * Returns the inverse of this physical unit. 498 * 499 * @return <code>1 / this</code> 500 */ 501 @Override 502 public final Unit<?> inverse() { 503 if (this.equals(ONE)) 504 return this; 505 return ProductUnit.ofQuotient(ONE, this); 506 } 507 508 /** 509 * Returns the result of dividing this unit by the specified divisor. If the 510 * factor is an integer value, the division is exact. For example: 511 * 512 * <pre> 513 * <code> 514 * QUART = GALLON_LIQUID_US.divide(4); // Exact definition. 515 * </code> 516 * </pre> 517 * 518 * @param divisor the divisor value. 519 * @return this unit divided by the specified divisor. 520 */ 521 @Override 522 public final Unit<Q> divide(Number divisor) { 523 if (Calculus.currentNumberSystem().isOne(divisor)) 524 return this; 525 Number factor = Calculator.of(divisor).reciprocal().peek(); 526 return transform(MultiplyConverter.of(factor)); 527 } 528 529 /** 530 * Returns the quotient of this unit with the one specified. 531 * 532 * @param that the unit divisor. 533 * @return <code>this.multiply(that.inverse())</code> 534 */ 535 @Override 536 public final Unit<?> divide(Unit<?> that) { 537 return this.multiply(that.inverse()); 538 } 539 540 /** 541 * Returns a unit equals to the given root of this unit. 542 * 543 * @param n the root's order. 544 * @return the result of taking the given root of this unit. 545 * @throws ArithmeticException if <code>n == 0</code> or if this operation would 546 * result in an unit with a fractional exponent. 547 */ 548 @Override 549 public final Unit<?> root(int n) { 550 if (n > 0) 551 return ProductUnit.ofRoot(this, n); 552 else if (n == 0) 553 throw new ArithmeticException("Root's order of zero"); 554 else 555 // n < 0 556 return ONE.divide(this.root(-n)); 557 } 558 559 /** 560 * Returns a unit equals to this unit raised to an exponent. 561 * 562 * @param n the exponent. 563 * @return the result of raising this unit to the exponent. 564 */ 565 @Override 566 public Unit<?> pow(int n) { 567 if (n > 0) 568 return this.multiply(this.pow(n - 1)); 569 else if (n == 0) 570 return ONE; 571 else 572 // n < 0 573 return ONE.divide(this.pow(-n)); 574 } 575 576 @Override 577 public Unit<Q> prefix(Prefix prefix) { 578 return this.transform(MultiplyConverter.ofPrefix(prefix)); 579 } 580 581 /** 582 * Compares this unit to the specified unit. The default implementation compares 583 * the name and symbol of both this unit and the specified unit, giving 584 * precedence to the symbol. 585 * 586 * @return a negative integer, zero, or a positive integer as this unit is less 587 * than, equal to, or greater than the specified unit. 588 */ 589 @Override 590 public int compareTo(Unit<Q> that) { 591 int symbolComparison = compareToWithPossibleNullValues(getSymbol(), that.getSymbol()); 592 if (symbolComparison == 0) { 593 return compareToWithPossibleNullValues(name, that.getName()); 594 } else { 595 return symbolComparison; 596 } 597 } 598 599 private int compareToWithPossibleNullValues(String a, String b) { 600 if (a == null) { 601 return (b == null) ? 0 : -1; 602 } else { 603 return (b == null) ? 1 : a.compareTo(b); 604 } 605 } 606 607 @Override 608 public boolean isEquivalentTo(Unit<Q> that) { 609 return this.getConverterTo(that).isIdentity(); 610 } 611 612 // ////////////////////////////////////////////////////////////// 613 // Ensures that sub-classes implement the hashCode method. 614 // ////////////////////////////////////////////////////////////// 615 616 @Override 617 public abstract boolean equals(Object obj); 618 619 @Override 620 public abstract int hashCode(); 621 622 /** 623 * Utility class for number comparison and equality 624 */ 625 protected static final class Equalizer { 626 /** 627 * Indicates if this unit is considered equals to the specified object. order). 628 * 629 * @param obj the object to compare for equality. 630 * @return <code>true</code> if <code>this</code> and <code>obj</code> are 631 * considered equal; <code>false</code>otherwise. 632 */ 633 public static boolean areEqual(@SuppressWarnings("rawtypes") Unit u1, 634 @SuppressWarnings("rawtypes") Unit u2) { 635 /* 636 * if (u1 != null && u2 != null) { if (u1.getName() != null && u1.getSymbol() != 637 * null) { return u1.getName().equals(u2.getName()) && 638 * u1.getSymbol().equals(u2.getSymbol()) && u1.internalIsCompatible(u2, false); 639 * } else if (u1.getSymbol() != null) { return 640 * u1.getSymbol().equals(u2.getSymbol()) && u1.internalIsCompatible(u2, false); 641 * } else { return u1.toString().equals(u2.toString()) && 642 * u1.internalIsCompatible(u2, false); } } else { 643 */ 644 if (u1 != null && u1.equals(u2)) 645 return true; 646 return false; 647 } 648 } 649}