001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2025, 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; 051import javax.measure.spi.SystemOfUnits; 052 053import org.apiguardian.api.API; 054 055import tech.units.indriya.format.LocalUnitFormat; 056import tech.units.indriya.format.SimpleUnitFormat; 057import tech.units.indriya.function.AbstractConverter; 058import tech.units.indriya.function.AddConverter; 059import tech.units.indriya.function.Calculus; 060import tech.units.indriya.function.MultiplyConverter; 061import tech.units.indriya.function.RationalNumber; 062import tech.units.indriya.internal.function.Calculator; 063import tech.units.indriya.spi.DimensionalModel; 064import tech.units.indriya.unit.AlternateUnit; 065import tech.units.indriya.unit.AnnotatedUnit; 066import tech.units.indriya.unit.ProductUnit; 067import tech.units.indriya.unit.TransformedUnit; 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.2, February 19, 2025 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#getInstance() 291 */ 292 @Override 293 public final <T extends Quantity<T>> Unit<T> asType(Class<T> type) { 294 return asType(type, Units.getInstance()); 295 /* 296 * Dimension typeDimension = UnitDimension.of(type); if (typeDimension != null 297 * && !typeDimension.equals(this.getDimension())) throw new 298 * ClassCastException("The unit: " + this + 299 * " is not compatible with quantities of type " + type); return (Unit<T>) this; 300 */ 301 } 302 303 @Override 304 public abstract Map<? extends Unit<?>, Integer> getBaseUnits(); 305 306 @Override 307 public abstract Dimension getDimension(); 308 309 protected void setName(String name) { 310 this.name = name; 311 } 312 313 public String getName() { 314 return name; 315 } 316 317 public String getSymbol() { 318 return symbol; 319 } 320 321 protected void setSymbol(String s) { 322 this.symbol = s; 323 } 324 325 @Override 326 public final UnitConverter getConverterTo(Unit<Q> that) throws UnconvertibleException { 327 return internalGetConverterTo(that, true); 328 } 329 330 @Override 331 public final UnitConverter getConverterToAny(Unit<?> that) throws IncommensurableException, UnconvertibleException { 332 return getConverterToAny(that, ABSOLUTE); 333 } 334 335 /** 336 * Casts this unit to a parameterized unit of specified nature or throw a 337 * ClassCastException if the dimension of the specified quantity and this unit's 338 * dimension do not match (regardless whether or not the dimensions are 339 * independent or not). 340 * 341 * @param type the quantity class identifying the nature of the unit. 342 * @param typeSystem the system of units using the quantity class. 343 * @throws ClassCastException if the dimension of this unit is different from 344 * the SI dimension of the specified type. 345 * @see javax.measure.spi.SystemOfUnits#getUnit(Class) 346 */ 347 @SuppressWarnings("unchecked") 348 public final <T extends Quantity<T>> Unit<T> asType(Class<T> type, SystemOfUnits typeSystem) { 349 Unit<T> typedUnit = typeSystem.getUnit(type); 350 final Dimension typeDimension = (typedUnit != null ? typedUnit.getDimension() : null); 351 if (typeDimension != null && !typeDimension.equals(this.getDimension())) 352 throw new ClassCastException("The unit: " + this + " is not compatible with quantities of type " + type); 353 return (Unit<T>) this; 354 } 355 356 /** 357 * Returns a converter of numeric values from this unit to another unit of same type. This method performs the same work as 358 * {@link #getConverterToAny(Unit)} without raising checked exception. 359 * 360 * @param that 361 * the unit of same type to which to convert the numeric values. 362 * @param scale the measurement scale. 363 * @return the converter from this unit to {@code that} unit in the given {@code scale}. 364 * @throws UnconvertibleException 365 * if a converter cannot be constructed. 366 * 367 * @see #getConverterToAny(Unit) 368 */ 369 @API(status=EXPERIMENTAL) 370 public final UnitConverter getConverterTo(Unit<Q> that, Scale scale) throws UnconvertibleException { 371 this.scale = scale; 372 return getConverterTo(that); 373 } 374 375 /** 376 * 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 377 * unknown at compile-time or when dimensional analysis allows for conversion between units of different type. 378 * 379 * <p> 380 * To convert to a unit having the same parameterized type, {@link #getConverterTo(Unit, Scale)} is preferred (no checked exception raised). 381 * </p> 382 * 383 * @param that 384 * the unit to which to convert the numeric values. 385 * @param scale the measurement scale. 386 * @return the converter from {@code this} unit to {@code that} unit using the given {@code scale}. 387 * @throws IncommensurableException 388 * if this unit is not {@linkplain #isCompatible(Unit) compatible} with {@code that}. 389 * @throws UnconvertibleException 390 * if a converter cannot be constructed. 391 * 392 * @see #getConverterTo(Unit) 393 * @see #isCompatible(Unit) 394 */ 395 @API(status=EXPERIMENTAL) 396 @SuppressWarnings("rawtypes") 397 public final UnitConverter getConverterToAny(Unit<?> that, Scale scale) throws IncommensurableException, UnconvertibleException { 398 if (!isCompatible(that)) 399 throw new IncommensurableException(this + " is not compatible with " + that); 400 this.scale = scale; 401 final AbstractUnit thatAbstr = (AbstractUnit) that; // Since both units are 402 // compatible they must both be abstract units. 403 final DimensionalModel model = DimensionalModel.current(); 404 Unit thisSystemUnit = this.getSystemUnit(); 405 UnitConverter thisToDimension = model.getDimensionalTransform(thisSystemUnit.getDimension()) 406 .concatenate(this.getSystemConverter()); 407 Unit thatSystemUnit = thatAbstr.getSystemUnit(); 408 UnitConverter thatToDimension = model.getDimensionalTransform(thatSystemUnit.getDimension()) 409 .concatenate(thatAbstr.getSystemConverter()); 410 return thatToDimension.inverse().concatenate(thisToDimension); 411 } 412 413 @Override 414 public final Unit<Q> alternate(String newSymbol) { 415 return new AlternateUnit<>(this, newSymbol); 416 } 417 418 @Override 419 public final Unit<Q> transform(UnitConverter operation) { 420 Unit<Q> systemUnit = this.getSystemUnit(); 421 UnitConverter cvtr; 422 if (this.isSystemUnit()) { 423 cvtr = this.getSystemConverter().concatenate(operation); 424 } else { 425 cvtr = operation; 426 } 427 return cvtr.isIdentity() ? systemUnit : new TransformedUnit<>(null, this, systemUnit, cvtr); 428 } 429 430 @Override 431 public final Unit<Q> shift(Number offset) { 432 if (Calculus.currentNumberSystem().isZero(offset)) 433 return this; 434 return transform(new AddConverter(offset)); 435 } 436 437 @Override 438 public final Unit<Q> multiply(Number factor) { 439 if (Calculus.currentNumberSystem().isOne(factor)) 440 return this; 441 return transform(MultiplyConverter.of(factor)); 442 } 443 444 @Override 445 public Unit<Q> shift(double offset) { 446 return shift(RationalNumber.of(offset)); 447 } 448 449 @Override 450 public Unit<Q> multiply(double multiplier) { 451 return multiply(RationalNumber.of(multiplier)); 452 } 453 454 @Override 455 public Unit<Q> divide(double divisor) { 456 return divide(RationalNumber.of(divisor)); 457 } 458 459 /** 460 * Internal helper for isCompatible 461 */ 462 private final boolean internalIsCompatible(Unit<?> that, boolean checkEquals) { 463 if (checkEquals) { 464 if (this == that || this.equals(that)) 465 return true; 466 } else { 467 if (this == that) 468 return true; 469 } 470 if (!(that instanceof Unit)) 471 return false; 472 Dimension thisDimension = this.getDimension(); 473 Dimension thatDimension = that.getDimension(); 474 if (thisDimension.equals(thatDimension)) 475 return true; 476 DimensionalModel model = DimensionalModel.current(); // Use 477 // dimensional 478 // analysis 479 // model. 480 return model.getFundamentalDimension(thisDimension).equals(model.getFundamentalDimension(thatDimension)); 481 } 482 483 protected final UnitConverter internalGetConverterTo(Unit<Q> that, boolean useEquals) 484 throws UnconvertibleException { 485 if (useEquals) { 486 if (this == that || this.equals(that)) 487 return AbstractConverter.IDENTITY; 488 } else { 489 if (this == that) 490 return AbstractConverter.IDENTITY; 491 } 492 Unit<Q> thisSystemUnit = this.getSystemUnit(); 493 Unit<Q> thatSystemUnit = that.getSystemUnit(); 494 if (!thisSystemUnit.equals(thatSystemUnit)) 495 try { 496 return getConverterToAny(that); 497 } catch (IncommensurableException e) { 498 throw new UnconvertibleException(e); 499 } 500 UnitConverter thisToSI = this.getSystemConverter(); 501 UnitConverter thatToSI = that.getConverterTo(thatSystemUnit); 502 return thatToSI.inverse().concatenate(thisToSI); 503 } 504 505 /** 506 * Returns the product of this physical unit with the one specified. 507 * 508 * @param that the physical unit multiplicand. 509 * @return <code>this * that</code> 510 */ 511 public final Unit<?> multiply(Unit<?> that) { 512 if (this.equals(ONE)) 513 return that; 514 if (that.equals(ONE)) 515 return this; 516 return ProductUnit.ofProduct(this, that); 517 } 518 519 /** 520 * Returns the inverse of this physical unit. 521 * 522 * @return <code>1 / this</code> 523 */ 524 @Override 525 public final Unit<?> inverse() { 526 if (this.equals(ONE)) 527 return this; 528 return ProductUnit.ofQuotient(ONE, this); 529 } 530 531 /** 532 * Returns the result of dividing this unit by the specified divisor. If the 533 * factor is an integer value, the division is exact. For example: 534 * 535 * <pre> 536 * <code> 537 * QUART = GALLON_LIQUID_US.divide(4); // Exact definition. 538 * </code> 539 * </pre> 540 * 541 * @param divisor the divisor value. 542 * @return this unit divided by the specified divisor. 543 */ 544 @Override 545 public final Unit<Q> divide(Number divisor) { 546 if (Calculus.currentNumberSystem().isOne(divisor)) 547 return this; 548 Number factor = Calculator.of(divisor).reciprocal().peek(); 549 return transform(MultiplyConverter.of(factor)); 550 } 551 552 /** 553 * Returns the quotient of this unit with the one specified. 554 * 555 * @param that the unit divisor. 556 * @return <code>this.multiply(that.inverse())</code> 557 */ 558 @Override 559 public final Unit<?> divide(Unit<?> that) { 560 return this.multiply(that.inverse()); 561 } 562 563 /** 564 * Returns a unit equals to the given root of this unit. 565 * 566 * @param n the root's order. 567 * @return the result of taking the given root of this unit. 568 * @throws ArithmeticException if <code>n == 0</code> or if this operation would 569 * result in an unit with a fractional exponent. 570 */ 571 @Override 572 public final Unit<?> root(int n) { 573 if (n > 0) 574 return ProductUnit.ofRoot(this, n); 575 else if (n == 0) 576 throw new ArithmeticException("Root's order of zero"); 577 else 578 // n < 0 579 return ONE.divide(this.root(-n)); 580 } 581 582 /** 583 * Returns a unit equals to this unit raised to an exponent. 584 * 585 * @param n the exponent. 586 * @return the result of raising this unit to the exponent. 587 */ 588 @Override 589 public Unit<?> pow(int n) { 590 if (n > 0) 591 return this.multiply(this.pow(n - 1)); 592 else if (n == 0) 593 return ONE; 594 else 595 // n < 0 596 return ONE.divide(this.pow(-n)); 597 } 598 599 @Override 600 public Unit<Q> prefix(Prefix prefix) { 601 return this.transform(MultiplyConverter.ofPrefix(prefix)); 602 } 603 604 /** 605 * Compares this unit to the specified unit. The default implementation compares 606 * the name and symbol of both this unit and the specified unit, giving 607 * precedence to the symbol. 608 * 609 * @return a negative integer, zero, or a positive integer as this unit is less 610 * than, equal to, or greater than the specified unit. 611 */ 612 @Override 613 public int compareTo(Unit<Q> that) { 614 int symbolComparison = compareToWithPossibleNullValues(getSymbol(), that.getSymbol()); 615 if (symbolComparison == 0) { 616 return compareToWithPossibleNullValues(name, that.getName()); 617 } else { 618 return symbolComparison; 619 } 620 } 621 622 private int compareToWithPossibleNullValues(String a, String b) { 623 if (a == null) { 624 return (b == null) ? 0 : -1; 625 } else { 626 return (b == null) ? 1 : a.compareTo(b); 627 } 628 } 629 630 @Override 631 public boolean isEquivalentTo(Unit<Q> that) { 632 return this.getConverterTo(that).isIdentity(); 633 } 634 635 // ////////////////////////////////////////////////////////////// 636 // Ensures that sub-classes implement the hashCode method. 637 // ////////////////////////////////////////////////////////////// 638 639 @Override 640 public abstract boolean equals(Object obj); 641 642 @Override 643 public abstract int hashCode(); 644 645 /** 646 * Utility class for number comparison and equality 647 */ 648 protected static final class Equalizer { 649 /** 650 * Indicates if this unit is considered equals to the specified object. order). 651 * 652 * @param obj the object to compare for equality. 653 * @return <code>true</code> if <code>this</code> and <code>obj</code> are 654 * considered equal; <code>false</code>otherwise. 655 */ 656 public static boolean areEqual(@SuppressWarnings("rawtypes") Unit u1, 657 @SuppressWarnings("rawtypes") Unit u2) { 658 /* 659 * if (u1 != null && u2 != null) { if (u1.getName() != null && u1.getSymbol() != 660 * null) { return u1.getName().equals(u2.getName()) && 661 * u1.getSymbol().equals(u2.getSymbol()) && u1.internalIsCompatible(u2, false); 662 * } else if (u1.getSymbol() != null) { return 663 * u1.getSymbol().equals(u2.getSymbol()) && u1.internalIsCompatible(u2, false); 664 * } else { return u1.toString().equals(u2.toString()) && 665 * u1.internalIsCompatible(u2, false); } } else { 666 */ 667 if (u1 != null && u1.equals(u2)) 668 return true; 669 return false; 670 } 671 } 672}