Unit.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.utils.units;

  18. import java.io.Serializable;
  19. import java.util.List;

  20. import org.hipparchus.CalculusFieldElement;
  21. import org.hipparchus.fraction.Fraction;
  22. import org.hipparchus.util.FastMath;
  23. import org.hipparchus.util.Precision;
  24. import org.orekit.errors.OrekitException;
  25. import org.orekit.errors.OrekitMessages;

  26. /** Basic handling of multiplicative units.
  27.  * <p>
  28.  * This class is by no means a complete handling of units. For complete
  29.  * support, look at libraries like {@code UOM}. This class handles only
  30.  * time, length, mass and current dimensions, as well as angles (which are
  31.  * dimensionless).
  32.  * </p>
  33.  * <p>
  34.  * Instances of this class are immutable.
  35.  * </p>
  36.  * @see <a href="https://github.com/netomi/uom">UOM</a>
  37.  * @author Luc Maisonobe
  38.  * @since 11.0
  39.  */
  40. public class Unit implements Serializable {

  41.     /** No unit. */
  42.     public static final Unit NONE = new Unit("n/a", 1.0, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO);

  43.     /** Dimensionless unit. */
  44.     public static final Unit ONE = new Unit("1", 1.0, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO);

  45.     /** Cycle unit.
  46.      * @since 13.0
  47.      */
  48.     public static final Unit CYCLE = new Unit("cyc", 1.0, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO);

  49.     /** Percentage unit. */
  50.     public static final Unit PERCENT = new Unit("%", 1.0e-2, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO);

  51.     /** Second unit. */
  52.     public static final Unit SECOND = new Unit("s", 1.0, Fraction.ZERO, Fraction.ZERO, Fraction.ONE, Fraction.ZERO, Fraction.ZERO);

  53.     /** Minute unit. */
  54.     public static final Unit MINUTE = SECOND.scale("min", 60.0);

  55.     /** Hour unit. */
  56.     public static final Unit HOUR = MINUTE.scale("h", 60);

  57.     /** Day unit. */
  58.     public static final Unit DAY = HOUR.scale("d", 24.0);

  59.     /** Julian year unit.
  60.      * @see <a href="https://www.iau.org/publications/proceedings_rules/units/">SI Units at IAU</a>
  61.      */
  62.     public static final Unit YEAR = DAY.scale("a", 365.25);

  63.     /** Hertz unit. */
  64.     public static final Unit HERTZ = SECOND.power("Hz", Fraction.MINUS_ONE);

  65.     /** Metre unit. */
  66.     public static final Unit METRE = new Unit("m", 1.0, Fraction.ZERO, Fraction.ONE, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO);

  67.     /** Kilometre unit. */
  68.     public static final Unit KILOMETRE = METRE.scale("km", 1000.0);

  69.     /** Kilogram unit. */
  70.     public static final Unit KILOGRAM = new Unit("kg", 1.0, Fraction.ONE, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO);

  71.     /** Gram unit. */
  72.     public static final Unit GRAM = KILOGRAM.scale("g", 1.0e-3);

  73.     /** Ampere unit. */
  74.     public static final Unit AMPERE = new Unit("A", 1.0, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ONE, Fraction.ZERO);

  75.     /** Radian unit. */
  76.     public static final Unit RADIAN = new Unit("rad", 1.0, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ONE);

  77.     /** Degree unit. */
  78.     public static final Unit DEGREE = RADIAN.scale("°", FastMath.toRadians(1.0));

  79.     /** Arc minute unit. */
  80.     public static final Unit ARC_MINUTE = DEGREE.scale("′", 1.0 / 60.0);

  81.     /** Arc second unit. */
  82.     public static final Unit ARC_SECOND = ARC_MINUTE.scale("″", 1.0 / 60.0);

  83.     /** Revolution unit. */
  84.     public static final Unit REVOLUTION = RADIAN.scale("rev", 2.0 * FastMath.PI);

  85.     /** Newton unit. */
  86.     public static final Unit NEWTON = KILOGRAM.multiply(null, METRE).divide("N", SECOND.power(null, Fraction.TWO));

  87.     /** Pascal unit. */
  88.     public static final Unit PASCAL = NEWTON.divide("Pa", METRE.power(null, Fraction.TWO));

  89.     /** Bar unit. */
  90.     public static final Unit BAR = PASCAL.scale("bar", 100000.0);

  91.     /** Joule unit. */
  92.     public static final Unit JOULE = NEWTON.multiply("J", METRE);

  93.     /** Watt unit. */
  94.     public static final Unit WATT = JOULE.divide("W", SECOND);

  95.     /** Coulomb unit. */
  96.     public static final Unit COULOMB = SECOND.multiply("C", AMPERE);

  97.     /** Volt unit. */
  98.     public static final Unit VOLT = WATT.divide("V", AMPERE);

  99.     /** Ohm unit. */
  100.     public static final Unit OHM = VOLT.divide("Ω", AMPERE);

  101.     /** tesla unit. */
  102.     public static final Unit TESLA = VOLT.multiply(null, SECOND).divide("T", METRE.power(null, Fraction.TWO));

  103.     /** Solar Flux Unit. */
  104.     public static final Unit SOLAR_FLUX_UNIT = WATT.divide(null, METRE.power(null, Fraction.TWO).multiply(null, HERTZ)).scale("SFU", 1.0e-22);

  105.     /** Total Electron Content Unit. */
  106.     public static final Unit TOTAL_ELECTRON_CONTENT_UNIT = METRE.power(null, new Fraction(-2)).scale("TECU", 1.0e+16);

  107.     /** Earth Radii used as Bstar unit in CCSDS OMM. */
  108.     public static final Unit EARTH_RADII = new Unit("ER", 1.0, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ONE, Fraction.ZERO);

  109.     /** Serializable UID. */
  110.     private static final long serialVersionUID = 20210402L;

  111.     /** Name name of the unit. */
  112.     private final String name;

  113.     /** Scaling factor to SI units. */
  114.     private final double scale;

  115.     /** Mass exponent. */
  116.     private final Fraction mass;

  117.     /** Length exponent. */
  118.     private final Fraction length;

  119.     /** Time exponent. */
  120.     private final Fraction time;

  121.     /** Current exponent. */
  122.     private final Fraction current;

  123.     /** Angle exponent. */
  124.     private final Fraction angle;

  125.     /** Simple constructor.
  126.      * @param name name of the unit
  127.      * @param scale scaling factor to SI units
  128.      * @param mass mass exponent
  129.      * @param length length exponent
  130.      * @param time time exponent
  131.      * @param current current exponent
  132.      * @param angle angle exponent
  133.      */
  134.     public Unit(final String name, final double scale,
  135.                 final Fraction mass, final Fraction length,
  136.                 final Fraction time, final Fraction current,
  137.                 final Fraction angle) {
  138.         this.name    = name;
  139.         this.scale   = scale;
  140.         this.mass    = mass;
  141.         this.length  = length;
  142.         this.time    = time;
  143.         this.current = current;
  144.         this.angle   = angle;
  145.     }

  146.     /** Get the name of the unit.
  147.      * @return name of the unit
  148.      */
  149.     public String getName() {
  150.         return name;
  151.     }

  152.     /** Get the scaling factor to SI units.
  153.      * @return scaling factor to SI units
  154.      */
  155.     public double getScale() {
  156.         return scale;
  157.     }

  158.     /** Get the mass exponent.
  159.      * @return mass exponent
  160.      */
  161.     public Fraction getMass() {
  162.         return mass;
  163.     }

  164.     /** Get the length exponent.
  165.      * @return length exponent
  166.      */
  167.     public Fraction getLength() {
  168.         return length;
  169.     }

  170.     /** Get the time exponent.
  171.      * @return time exponent
  172.      */
  173.     public Fraction getTime() {
  174.         return time;
  175.     }

  176.     /** Get the current exponent.
  177.      * @return current exponent
  178.      */
  179.     public Fraction getCurrent() {
  180.         return current;
  181.     }

  182.     /** Get the angle exponent.
  183.      * @return angle exponent
  184.      */
  185.     public Fraction getAngle() {
  186.         return angle;
  187.     }

  188.     /** Check if a unit has the same dimension as another unit.
  189.      * @param other other unit to check against
  190.      * @return true if unit has the same dimension as the other unit
  191.      */
  192.     public boolean sameDimension(final Unit other) {
  193.         return time.equals(other.time) && length.equals(other.length)   &&
  194.                mass.equals(other.mass) && current.equals(other.current) &&
  195.                angle.equals(other.angle);
  196.     }

  197.     /** Create the SI unit with same dimension.
  198.      * @return a new unit, with same dimension as instance and scaling factor set to 1.0
  199.      */
  200.     public Unit sameDimensionSI() {
  201.         final StringBuilder builder = new StringBuilder();
  202.         append(builder, KILOGRAM.name, mass);
  203.         append(builder, METRE.name,    length);
  204.         append(builder, SECOND.name,   time);
  205.         append(builder, AMPERE.name,   current);
  206.         append(builder, RADIAN.name,   angle);
  207.         if (builder.length() == 0) {
  208.             builder.append('1');
  209.         }
  210.         return new Unit(builder.toString(), 1.0, mass, length, time, current, angle);
  211.     }

  212.     /** Ensure some units are compatible with reference units.
  213.      * @param description description of the units list (for error message generation)
  214.      * @param reference reference units
  215.      * @param units units to check
  216.      * @param allowScaleDifferences if true, unit with same dimension but different
  217.      * scale (like {@link #KILOMETRE} versus {@link #METRE}) are allowed, otherwise they will trigger an exception
  218.      * @exception OrekitException if units are not compatible (number of elements, dimensions or scaling)
  219.      */
  220.     public static void ensureCompatible(final String description, final List<Unit> reference,
  221.                                         final boolean allowScaleDifferences, final List<Unit> units) {
  222.         if (units.size() != reference.size()) {
  223.             throw new OrekitException(OrekitMessages.WRONG_NB_COMPONENTS,
  224.                                       description, reference.size(), units.size());
  225.         }
  226.         for (int i = 0; i < reference.size(); ++i) {
  227.             if (!reference.get(i).sameDimension(units.get(i))) {
  228.                 throw new OrekitException(OrekitMessages.INCOMPATIBLE_UNITS,
  229.                                           reference.get(i).getName(),
  230.                                           units.get(i).getName());
  231.             }
  232.             if (!(allowScaleDifferences ||
  233.                   Precision.equals(reference.get(i).getScale(), units.get(i).getScale(), 1))) {
  234.                 throw new OrekitException(OrekitMessages.INCOMPATIBLE_UNITS,
  235.                                           reference.get(i).getName(),
  236.                                           units.get(i).getName());
  237.             }
  238.         }
  239.     }

  240.     /** Append a dimension contribution to a unit name.
  241.      * @param builder builder for unit name
  242.      * @param dim name of the dimension
  243.      * @param exp exponent of the dimension
  244.      */
  245.     private void append(final StringBuilder builder, final String dim, final Fraction exp) {
  246.         if (!exp.isZero()) {
  247.             if (builder.length() > 0) {
  248.                 builder.append('.');
  249.             }
  250.             builder.append(dim);
  251.             if (exp.getDenominator() == 1) {
  252.                 if (exp.getNumerator() != 1) {
  253.                     builder.append(Integer.toString(exp.getNumerator()).
  254.                                    replace('-', '⁻').
  255.                                    replace('0', '⁰').
  256.                                    replace('1', '¹').
  257.                                    replace('2', '²').
  258.                                    replace('3', '³').
  259.                                    replace('4', '⁴').
  260.                                    replace('5', '⁵').
  261.                                    replace('6', '⁶').
  262.                                    replace('7', '⁷').
  263.                                    replace('8', '⁸').
  264.                                    replace('9', '⁹'));
  265.                 }
  266.             } else {
  267.                 builder.
  268.                     append("^(").
  269.                     append(exp.getNumerator()).
  270.                     append('/').
  271.                     append(exp.getDenominator()).
  272.                     append(')');
  273.             }
  274.         }
  275.     }

  276.     /** Create an alias for a unit.
  277.      * @param newName name of the new unit
  278.      * @return a new unit representing same unit as the instance but with a different name
  279.      */
  280.     public Unit alias(final String newName) {
  281.         return new Unit(newName, scale, mass, length, time, current, angle);
  282.     }

  283.     /** Scale a unit.
  284.      * @param newName name of the new unit
  285.      * @param factor scaling factor
  286.      * @return a new unit representing scale times the instance
  287.      */
  288.     public Unit scale(final String newName, final double factor) {
  289.         return new Unit(newName, factor * scale, mass, length, time, current, angle);
  290.     }

  291.     /** Create power of unit.
  292.      * @param newName name of the new unit
  293.      * @param exponent exponent to apply
  294.      * @return a new unit representing the power of the instance
  295.      */
  296.     public Unit power(final String newName, final Fraction exponent) {

  297.         final int num = exponent.getNumerator();
  298.         final int den = exponent.getDenominator();
  299.         double s = (num == 1) ? scale : FastMath.pow(scale, num);
  300.         if (den > 1) {
  301.             if (den == 2) {
  302.                 s = FastMath.sqrt(s);
  303.             } else if (den == 3) {
  304.                 s = FastMath.cbrt(s);
  305.             } else {
  306.                 s = FastMath.pow(s, 1.0 / den);
  307.             }
  308.         }

  309.         return new Unit(newName, s,
  310.                         mass.multiply(exponent), length.multiply(exponent),
  311.                         time.multiply(exponent), current.multiply(current),
  312.                         angle.multiply(exponent));
  313.     }

  314.     /** Create root of unit.
  315.      * @param newName name of the new unit
  316.      * @return a new unit representing the square root of the instance
  317.      */
  318.     public Unit sqrt(final String newName) {
  319.         return new Unit(newName, FastMath.sqrt(scale),
  320.                         mass.divide(2), length.divide(2),
  321.                         time.divide(2), current.divide(2),
  322.                         angle.divide(2));
  323.     }

  324.     /** Create product of units.
  325.      * @param newName name of the new unit
  326.      * @param other unit to multiply with
  327.      * @return a new unit representing the this times the other unit
  328.      */
  329.     public Unit multiply(final String newName, final Unit other) {
  330.         return new Unit(newName, scale * other.scale,
  331.                         mass.add(other.mass), length.add(other.length),
  332.                         time.add(other.time), current.add(other.current),
  333.                         angle.add(other.angle));
  334.     }

  335.     /** Create quotient of units.
  336.      * @param newName name of the new unit
  337.      * @param other unit to divide with
  338.      * @return a new unit representing the this divided by the other unit
  339.      */
  340.     public Unit divide(final String newName, final Unit other) {
  341.         return new Unit(newName, scale / other.scale,
  342.                         mass.subtract(other.mass), length.subtract(other.length),
  343.                         time.subtract(other.time), current.subtract(other.current),
  344.                         angle.subtract(other.angle));
  345.     }

  346.     /** Convert a value to SI units.
  347.      * @param value value instance unit
  348.      * @return value in SI units
  349.      */
  350.     public double toSI(final double value) {
  351.         return value * scale;
  352.     }

  353.     /** Convert a value to SI units.
  354.      * @param value value instance unit
  355.      * @return value in SI units
  356.      */
  357.     public double toSI(final Double value) {
  358.         return value == null ? Double.NaN : value * scale;
  359.     }

  360.     /** Convert a value to SI units.
  361.      * @param <T> type of the field elements
  362.      * @param value value instance unit
  363.      * @return value in SI units
  364.      * @since 12.1
  365.      */
  366.     public <T extends CalculusFieldElement<T>> T toSI(final T value) {
  367.         return value.multiply(scale);
  368.     }

  369.     /** Convert a value from SI units.
  370.      * @param value value SI unit
  371.      * @return value in instance units
  372.      */
  373.     public double fromSI(final double value) {
  374.         return value / scale;
  375.     }

  376.     /** Convert a value from SI units.
  377.      * @param value value SI unit
  378.      * @return value in instance units
  379.      */
  380.     public double fromSI(final Double value) {
  381.         return value == null ? Double.NaN : value / scale;
  382.     }

  383.     /** Convert a value from SI units.
  384.      * @param <T> type of the field elements
  385.      * @param value value SI unit
  386.      * @return value in instance units
  387.      */
  388.     public <T extends CalculusFieldElement<T>> T fromSI(final T value) {
  389.         return value.divide(scale);
  390.     }

  391.     /** Parse a unit.
  392.      * <p>
  393.      * The grammar for unit specification allows chains units multiplication and
  394.      * division, as well as putting powers on units.
  395.      * </p>
  396.      * <p>The symbols used for units are the SI units with some extensions.
  397.      * </p>
  398.      * <dl>
  399.      *   <dt>year</dt>
  400.      *   <dd>the accepted non-SI unit for Julian year is "a" but we also accept "yr"</dd>
  401.      *   <dt>day</dt>
  402.      *   <dd>the accepted non-SI unit for day is "d" but we also accept "day"</dd>
  403.      *   <dt>dimensionless</dt>
  404.      *   <dd>both "1" and "#" (U+0023, NUMBER SIGN) are accepted</dd>
  405.      *   <dt>mass</dt>
  406.      *   <dd>"g" is the standard symbol, despite the unit is "kg" (it is the only
  407.      *       unit that has a prefix in its name, so all multiples must be based on "g")</dd>
  408.      *   <dt>degrees</dt>
  409.      *   <dd>the base symbol for degrees is "°" (U+00B0, DEGREE SIGN), but we also accept
  410.      *       "◦" (U+25E6, WHITE BULLET) and "deg"</dd>
  411.      *   <dt>arcminute</dt>
  412.      *   <dd>The base symbol for arcminute is "′" (U+2032, PRIME) but we also accept "'" (U+0027, APOSTROPHE)</dd>
  413.      *   <dt>arcsecond</dt>
  414.      *   <dd>The base symbol for arcsecond is "″" (U+2033, DOUBLE PRIME) but we also accept
  415.      *   "''" (two occurrences of U+0027, APOSTROPHE), "\"" (U+0022, QUOTATION MARK) and "as"</dd>
  416.      * </dl>
  417.      * <p>
  418.      * All the SI prefix (from "y", yocto, to "Y", Yotta) are accepted, as well
  419.      * as integer prefixes. The standard symbol for micro 10⁻⁶ is "µ" (U+00B5, MICRO SIGN),
  420.      * but we also accept "μ" (U+03BC, GREEK SMALL LETTER MU). Beware that some combinations
  421.      * are forbidden, for example "Pa" is Pascal, not peta-years, and "as" is arcsecond for
  422.      * this parser, not atto-seconds, because many people in the space field use mas for
  423.      * milliarcseconds and µas for microarcseconds. Beware that prefixes are case-sensitive!
  424.      * Integer prefixes can be used to specify units like "30s", but only once at the beginning
  425.      * of the specification (i.e. "2rev/d²" is accepted, but "rev/(2d)²" is refused). Conforming
  426.      * with SI brochure "The International System of Units" (9th edition, 2019), each SI
  427.      * prefix is part of the unit and precedes the unit symbol without a separator
  428.      * (i.e. MHz is seen as one identifier).
  429.      * </p>
  430.      * <dl>
  431.      *   <dt>multiplication</dt>
  432.      *   <dd>can specified with either "*" (U+002A, ASTERISK), "×" (U+00D7, MULTIPLICATION SIGN),
  433.      *   "." (U+002E, FULL STOP) or "·" (U+00B7, MIDDLE DOT) as the operator</dd>
  434.      *   <dt>division</dt>
  435.      *   <dd>can be specified with either "/" (U+002F, SOLIDUS) or "⁄" (U+2044, FRACTION SLASH)
  436.      *   as the operator</dd>
  437.      *   <dt>powers</dt>
  438.      *   <dd>can be specified either by
  439.      *     <ul>
  440.      *       <li>prefixing with the unicode "√" (U+221A, SQUARE ROOT) character</li>
  441.      *       <li>postfixing with "**", "^" or implicitly using unicode superscripts</li>
  442.      *     </ul>
  443.      *   </dd>
  444.      * </dl>
  445.      * <p>
  446.      * Exponents can be specified in different ways:
  447.      * <ul>
  448.      *   <li>as an integer, as in "m^-2" or "m⁻²"</li>
  449.      *   <li>directly as unicode characters for the few fractions that unicode supports, as in "Ω^⅞"</li>
  450.      *   <li>as the special decimal value 0.5 which is used by CCSDS, as in "km**0.5"</li>
  451.      *   <li>as a pair of parentheses surrounding two integers separated by a solidus or fraction slash,
  452.      *   as in "Pa^(11/12)"</li>
  453.      * </ul>
  454.      * For integer exponents, the digits must be ASCII digits from the Basic Latin block from
  455.      * unicode if explicit exponent marker "**" or "^" is used, or using unicode superscript
  456.      * digits if implicit exponentiation (i.e. no markers at all) is used. Unicode superscripts
  457.      * are not allowed for fractional exponents because unicode does not provide a superscript solidus.
  458.      * Negative exponents can be used too.
  459.      * <p>
  460.      * These rules mean all the following (silly) examples are parsed properly:
  461.      * MHz, km/√d, kg.m.s⁻¹, µas^⅖/(h**(2)×m)³, km/√(kg.s), km**0.5, 2rev/d²
  462.      * </p>
  463.      * @param unitSpecification unit specification to parse
  464.      * @return parsed unit
  465.      */
  466.     public static Unit parse(final String unitSpecification) {

  467.         // parse the specification
  468.         final List<PowerTerm> terms = Parser.buildTermsList(unitSpecification);

  469.         if (terms == null) {
  470.             // special handling of "n/a"
  471.             return Unit.NONE;
  472.         }

  473.         // build compound unit
  474.         Unit unit = Unit.ONE;
  475.         for (final PowerTerm term : terms) {
  476.             try {
  477.                 Unit u = PrefixedUnit.valueOf(term.getBase().toString());
  478.                 if (!Fraction.ONE.equals(term.getExponent())) {
  479.                     u = u.power(null, term.getExponent());
  480.                 }
  481.                 u = u.scale(null, term.getScale());
  482.                 unit = unit.multiply(null, u);
  483.             } catch (IllegalArgumentException iae) {
  484.                 throw new OrekitException(OrekitMessages.UNKNOWN_UNIT, term.getBase());
  485.             }
  486.         }

  487.         // give final name to unit
  488.         return unit.alias(unitSpecification);

  489.     }

  490.     /** Check if the instance represents the same unit as another instance.
  491.      * <p>
  492.      * The name is not considered so aliases are considered equal.
  493.      * </p>
  494.      * @param unit other unit
  495.      * @return true if the instance and the other unit refer to the same unit
  496.      */
  497.     public boolean equals(final Object unit) {

  498.         if (unit == this) {
  499.             // first fast check
  500.             return true;
  501.         }

  502.         if (unit instanceof Unit) {
  503.             final Unit u = (Unit) unit;
  504.             return Precision.equals(scale, u.scale, 1) &&
  505.                    mass.equals(u.mass) && length.equals(u.length) && time.equals(u.time) &&
  506.                    current.equals(u.current) && angle.equals(u.angle);
  507.         }

  508.         return false;

  509.     }

  510.     /** Get a hashcode for this unit.
  511.      * @return hashcode
  512.      */
  513.     public int hashCode() {
  514.         return 0x67e7 ^
  515.                (Double.hashCode(scale) << 12) ^
  516.                (mass.hashCode()        << 10) ^
  517.                (length.hashCode()      <<  8) ^
  518.                (time.hashCode()        <<  6) ^
  519.                (current.hashCode()     <<  4) ^
  520.                (angle.hashCode()       <<  2);
  521.     }

  522.     /** {@inheritDoc} */
  523.     @Override
  524.     public String toString() {
  525.         return getName();
  526.     }

  527. }