View Javadoc
1   package org.djunits.unit;
2   
3   import java.io.Serializable;
4   import java.util.Arrays;
5   import java.util.LinkedHashSet;
6   import java.util.Set;
7   
8   import org.djunits.quantity.Quantities;
9   import org.djunits.quantity.Quantity;
10  import org.djunits.unit.scale.IdentityScale;
11  import org.djunits.unit.scale.LinearScale;
12  import org.djunits.unit.scale.OffsetLinearScale;
13  import org.djunits.unit.scale.Scale;
14  import org.djunits.unit.si.SIDimensions;
15  import org.djunits.unit.si.SIPrefix;
16  import org.djunits.unit.si.SIPrefixes;
17  import org.djunits.unit.unitsystem.UnitSystem;
18  import org.djunits.unit.util.UnitRuntimeException;
19  import org.djutils.exceptions.Throw;
20  
21  /**
22   * All units are internally <i>stored</i> relative to a standard unit with conversion factor. This means that e.g., a meter is
23   * stored with conversion factor 1.0, whereas kilometer is stored with a conversion factor 1000.0. This means that if we want to
24   * express a length meter in kilometers, we have to <i>divide</i> by the conversion factor.
25   * <p>
26   * Copyright (c) 2019-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
27   * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>
28   * </p>
29   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
30   * @param <U> the unit to reference the actual unit in return values
31   */
32  public class Unit<U extends Unit<U>> implements Serializable, Cloneable
33  {
34      /** */
35      private static final long serialVersionUID = 20190818L;
36  
37      /** The id of the unit; has to be unique within the unit name. Used for, e.g., localization and retrieval. */
38      private String id;
39  
40      /** The abbreviations in the default locale. All abbreviations an be used in the valueOf() and of() methods. */
41      private Set<String> abbreviations;
42  
43      /** The default display abbreviation in the default locale for printing. Included in the abbreviations list. */
44      private String defaultDisplayAbbreviation;
45  
46      /** The default textual abbreviation in the default locale for data entry. Included in the abbreviations list. */
47      private String defaultTextualAbbreviation;
48  
49      /** The long name of the unit in the default locale. */
50      private String name;
51  
52      /** The scale to use to convert between this unit and the standard (e.g., SI, BASE) unit. */
53      private Scale scale;
54  
55      /** The unit system, e.g. SI or Imperial. */
56      private UnitSystem unitSystem;
57  
58      /** Has the unit been automatically generated or not. */
59      private boolean generated;
60  
61      /** Does the unit have the standard SI signature? */
62      private boolean baseSIUnit;
63  
64      /**
65       * The corresponding unit base that contains all registered units for the unit as well as SI dimension information. The base
66       * unit of a unit base is null.
67       */
68      private Quantity<U> quantity;
69  
70      // TODO create a static that loads all unit classes in the registry
71  
72      /**
73       * Initialize a blank unit that can be built through reflection with several 'setter' methods followed by calling the
74       * build(...) method.
75       */
76      protected Unit()
77      {
78          //
79      }
80  
81      /**
82       * Build the unit, using the information of the provided builder class. First check rigorously if all necessary fields in
83       * the builder have been set, and whether they are consistent and valid. The behavior is as follows: the defaultAbbreviation
84       * and defaultTextualAbbreviation are added to the abbreviations set, if they are not yet already there. When the
85       * defaultAbbreviation is set and the defaultTextualAbbreviation is not set, the defaultTextualAbbreviation gets the value
86       * of defaultAbbreviation. The reverse also holds: When the defaultTextualAbbreviation is set and the defaultAbbreviation is
87       * not set, the defaultAbbreviation gets the value of defaultTextualAbbreviation. When neither the
88       * defaultTextualAbbreviation, nor the defaultAbbreviation are set, both get the value of the unitId provided in the
89       * builder.
90       * <p>
91       * Note that the unit's name and id can be blank, as long as the SI units show that the unit is dimensionless, and the scale
92       * is the IdentityScale.
93       * </p>
94       * @param builder Builder&lt;U&gt;; Builder&lt;U&gt; the object that contains the information about the construction of the
95       *            class
96       * @return U; the constructed unit
97       * @throws UnitRuntimeException when not all fields have been set
98       */
99      @SuppressWarnings("unchecked")
100     public U build(final Builder<U> builder) throws UnitRuntimeException
101     {
102         // Check the validity
103         String cName = getClass().getSimpleName();
104         Throw.whenNull(builder.getId(), "Constructing unit %s: id cannot be null", cName);
105         Throw.when(
106                 builder.getId().length() == 0
107                         && !(builder.getSiPrefixes().equals(SIPrefixes.NONE) && builder.getScale().equals(IdentityScale.SCALE)),
108                 UnitRuntimeException.class, "Constructing unit %s: id.length cannot be 0", cName);
109         String unitId = builder.getId();
110         Throw.whenNull(builder.getQuantity(), "Constructing unit %s.%s: baseUnit cannot be null", cName, unitId);
111         Throw.whenNull(builder.getName(), "Constructing unit %s.%s: name cannot be null", cName, unitId);
112         Throw.when(
113                 builder.getName().length() == 0
114                         && !(builder.getSiPrefixes().equals(SIPrefixes.NONE) && builder.getScale().equals(IdentityScale.SCALE)),
115                 UnitRuntimeException.class, "Constructing unit %s.%s: name.length cannot be 0", cName, unitId);
116         Throw.whenNull(builder.getScale(), "Constructing unit %s.%s: scale cannot be null", cName, unitId);
117         Throw.whenNull(builder.getUnitSystem(), "Constructing unit %s.%s: unitSystem cannot be null", cName, unitId);
118 
119         // set the key fields
120         this.id = unitId;
121         this.name = builder.getName();
122         this.quantity = builder.getQuantity();
123         this.unitSystem = builder.getUnitSystem();
124         this.scale = builder.getScale();
125         this.generated = builder.isGenerated();
126         this.baseSIUnit = this.scale.isBaseSIScale();
127 
128         // Set and check the abbreviations
129         if (builder.getDefaultDisplayAbbreviation() == null)
130         {
131             if (builder.getDefaultTextualAbbreviation() == null)
132             {
133                 this.defaultDisplayAbbreviation = unitId;
134             }
135             else
136             {
137                 this.defaultDisplayAbbreviation = builder.getDefaultTextualAbbreviation();
138             }
139         }
140         else
141         {
142             this.defaultDisplayAbbreviation = builder.getDefaultDisplayAbbreviation();
143         }
144         if (builder.getDefaultTextualAbbreviation() == null)
145         {
146             this.defaultTextualAbbreviation = this.defaultDisplayAbbreviation;
147         }
148         else
149         {
150             this.defaultTextualAbbreviation = builder.getDefaultTextualAbbreviation();
151         }
152         this.abbreviations = new LinkedHashSet<>();
153         this.abbreviations.add(this.defaultDisplayAbbreviation);
154         this.abbreviations.add(this.defaultTextualAbbreviation);
155         this.abbreviations.addAll(builder.getAdditionalAbbreviations());
156 
157         // See what SI prefixes have to be registered. If not specified: NONE.
158         SIPrefixes siPrefixes = builder.getSiPrefixes() == null ? SIPrefixes.NONE : builder.getSiPrefixes();
159 
160         // Register the unit, possibly including all SI prefixes
161         this.quantity.registerUnit((U) this, siPrefixes, builder.getSiPrefixPowerFactor());
162         return (U) this;
163     }
164 
165     /**
166      * Create a scaled version of this unit with the same unit system but another SI prefix and scale.
167      * @param siPrefix SIPrefix; the prefix for which to scale the unit
168      * @param siPrefixPower double; the power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters.
169      * @param automaticallyGenerated boolean; indicate whether the unit has been automatically generated
170      * @return U; a scaled instance of this unit
171      * @throws UnitRuntimeException when cloning fails
172      */
173     public U deriveSI(final SIPrefix siPrefix, final double siPrefixPower, final boolean automaticallyGenerated)
174     {
175         Throw.whenNull(siPrefix, "siPrefix cannot be null");
176         try
177         {
178             U clone = clone();
179 
180             // Get values: combine all prefixes with all names / abbreviations
181             String cloneId = siPrefix.getDefaultTextualPrefix() + clone.getId();
182             String cloneName = clone.getName();
183             if (cloneName.startsWith("square "))
184             {
185                 cloneName = "square " + siPrefix.getPrefixName() + cloneName.substring(6);
186             }
187             else if (clone.getName().startsWith("cubic "))
188             {
189                 cloneName = "cubic " + siPrefix.getPrefixName() + cloneName.substring(5);
190             }
191             else
192             {
193                 cloneName = siPrefix.getPrefixName() + cloneName;
194             }
195             Set<String> cloneAbbreviations = new LinkedHashSet<>();
196             for (String abbreviation : clone.getDefaultAbbreviations())
197             {
198                 cloneAbbreviations.add(siPrefix.getDefaultTextualPrefix() + abbreviation);
199             }
200             String cloneDefaultAbbreviation = siPrefix.getDefaultDisplayPrefix() + clone.getDefaultDisplayAbbreviation();
201             String cloneDefaultTextualAbbreviation = siPrefix.getDefaultTextualPrefix() + clone.getDefaultTextualAbbreviation();
202 
203             // Make a builder and set values
204             Builder<U> builder = makeBuilder();
205             builder.setId(cloneId);
206             builder.setName(cloneName);
207             builder.setQuantity(this.quantity);
208             builder.setSiPrefixes(SIPrefixes.NONE, 1.0);
209             builder.setDefaultDisplayAbbreviation(cloneDefaultAbbreviation);
210             builder.setDefaultTextualAbbreviation(cloneDefaultTextualAbbreviation);
211             builder.setAdditionalAbbreviations(cloneAbbreviations.toArray(new String[cloneAbbreviations.size()]));
212             if (getScale() instanceof OffsetLinearScale)
213             {
214                 builder.setScale(new OffsetLinearScale(
215                         (siPrefixPower == 1.0 ? siPrefix.getFactor() : Math.pow(siPrefix.getFactor(), siPrefixPower))
216                                 * ((LinearScale) getScale()).getConversionFactorToStandardUnit(),
217                         0.0));
218             }
219             else
220             {
221                 builder.setScale(new LinearScale(
222                         (siPrefixPower == 1.0 ? siPrefix.getFactor() : Math.pow(siPrefix.getFactor(), siPrefixPower))
223                                 * ((LinearScale) getScale()).getConversionFactorToStandardUnit()));
224             }
225             builder.setUnitSystem(this.unitSystem); // SI_BASE stays SI_BASE with prefix
226             builder.setGenerated(automaticallyGenerated);
227 
228             return clone.build(builder);
229         }
230         catch (CloneNotSupportedException exception)
231         {
232             throw new UnitRuntimeException(exception);
233         }
234     }
235 
236     /**
237      * Create a scaled version of this unit with the same unit system but another SI prefix and scale. This method is used for a
238      * unit that is explicitly scaled with an SI prefix.
239      * @param siPrefix SIPrefix; the prefix for which to scale the unit
240      * @param siPrefixPower double; the power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters.
241      * @return a scaled instance of this unit
242      * @throws UnitRuntimeException when cloning fails
243      */
244     public U deriveSI(final SIPrefix siPrefix, final double siPrefixPower)
245     {
246         return deriveSI(siPrefix, siPrefixPower, false);
247     }
248 
249     /**
250      * Create a scaled version of this unit with the same unit system but another SI prefix and scale, where the "k" and "kilo"
251      * abbreviations at the start will be replaced by the new information from the SIPrefix.
252      * @param siPrefix SIPrefix; the prefix for which to scale the unit
253      * @param siPrefixPower double; the power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters.
254      * @param automaticallyGenerated boolean; indicate whether the unit has been automatically generated
255      * @return U; a scaled instance of this unit
256      * @throws UnitRuntimeException when cloning fails
257      */
258     public U deriveSIKilo(final SIPrefix siPrefix, final double siPrefixPower, final boolean automaticallyGenerated)
259     {
260         Throw.whenNull(siPrefix, "siPrefix cannot be null");
261         Throw.when(!this.id.startsWith("k"), UnitRuntimeException.class, "deriving from a kilo-unit: id should start with 'k'");
262         Throw.when(!this.defaultTextualAbbreviation.startsWith("k"), UnitRuntimeException.class,
263                 "deriving from a kilo-unit: defaultTextualAbbreviation should start with 'k'");
264         Throw.when(!this.defaultDisplayAbbreviation.startsWith("k"), UnitRuntimeException.class,
265                 "deriving from a kilo-unit: defaultDisplayAbbreviation should start with 'k'");
266         Throw.when(!this.name.startsWith("kilo"), UnitRuntimeException.class,
267                 "deriving from a kilo-unit: name should start with 'kilo'");
268         for (String abbreviation : getDefaultAbbreviations())
269         {
270             Throw.when(!abbreviation.startsWith("k"), UnitRuntimeException.class,
271                     "deriving from a kilo-unit: each abbreviation should start with 'k'");
272         }
273 
274         try
275         {
276             U clone = clone();
277 
278             // get values: combine all prefixes with all names / abbreviations
279             String cloneId = siPrefix.getDefaultTextualPrefix() + clone.getId().substring(1);
280             String cloneName = siPrefix.getPrefixName() + clone.getName().substring(4);
281             Set<String> cloneAbbreviations = new LinkedHashSet<>();
282             for (String abbreviation : clone.getDefaultAbbreviations())
283             {
284                 cloneAbbreviations.add(siPrefix.getDefaultTextualPrefix() + abbreviation.substring(1));
285             }
286             String cloneDefaultAbbreviation =
287                     siPrefix.getDefaultDisplayPrefix() + clone.getDefaultDisplayAbbreviation().substring(1);
288             String cloneDefaultTextualAbbreviation =
289                     siPrefix.getDefaultTextualPrefix() + clone.getDefaultTextualAbbreviation().substring(1);
290 
291             // make a builder and set values
292             Builder<U> builder = makeBuilder();
293             builder.setId(cloneId);
294             builder.setName(cloneName);
295             builder.setQuantity(this.quantity);
296             builder.setSiPrefixes(SIPrefixes.NONE, 1.0);
297             builder.setDefaultDisplayAbbreviation(cloneDefaultAbbreviation);
298             builder.setDefaultTextualAbbreviation(cloneDefaultTextualAbbreviation);
299             builder.setAdditionalAbbreviations(cloneAbbreviations.toArray(new String[cloneAbbreviations.size()]));
300             if (getScale() instanceof OffsetLinearScale)
301             {
302                 builder.setScale(new OffsetLinearScale(
303                         (siPrefixPower == 1.0 ? siPrefix.getFactor() : Math.pow(siPrefix.getFactor(), siPrefixPower))
304                                 * ((LinearScale) getScale()).getConversionFactorToStandardUnit(),
305                         0.0));
306             }
307             else
308             {
309                 builder.setScale(new LinearScale(
310                         (siPrefixPower == 1.0 ? siPrefix.getFactor() : Math.pow(siPrefix.getFactor(), siPrefixPower))
311                                 * ((LinearScale) getScale()).getConversionFactorToStandardUnit()));
312             }
313 
314             builder.setUnitSystem(this.unitSystem);
315             builder.setGenerated(automaticallyGenerated);
316 
317             return clone.build(builder);
318         }
319         catch (CloneNotSupportedException exception)
320         {
321             throw new UnitRuntimeException(exception);
322         }
323     }
324 
325     /**
326      * Create a scaled version of this unit with the same unit system but another SI prefix and scale. The "per" units scale in
327      * the opposite direction as the normally scaled units. It will yield units like "/ms", "/mus", "/ns", etc.
328      * @param siPrefix SIPrefix; the prefix for which to scale the unit
329      * @param siPrefixPower double; the power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters.
330      * @param automaticallyGenerated boolean; indicate whether the unit has been automatically generated
331      * @return U; a scaled instance of this unit
332      * @throws UnitRuntimeException when cloning fails
333      */
334     public U derivePerSI(final SIPrefix siPrefix, final double siPrefixPower, final boolean automaticallyGenerated)
335     {
336         Throw.whenNull(siPrefix, "siPrefix cannot be null");
337         try
338         {
339             U clone = clone();
340 
341             // Get values: combine all prefixes with all names / abbreviations
342             String cloneId = siPrefix.getDefaultTextualPrefix() + clone.getId().replace("1/", "").replace("/", "").trim();
343             String cloneName =
344                     siPrefix.getPrefixName() + clone.getName().replace("per", "").replace("1/", "").replace("/", "").trim();
345             Set<String> cloneAbbreviations = new LinkedHashSet<>();
346             for (String abbreviation : clone.getDefaultAbbreviations())
347             {
348                 cloneAbbreviations
349                         .add(siPrefix.getDefaultTextualPrefix() + abbreviation.replace("1/", "").replace("/", "").trim());
350                 cloneAbbreviations
351                         .add("1" + siPrefix.getDefaultTextualPrefix() + abbreviation.replace("1/", "").replace("/", "").trim());
352             }
353             String cloneDefaultAbbreviation = siPrefix.getDefaultDisplayPrefix()
354                     + clone.getDefaultDisplayAbbreviation().replace("1/", "").replace("/", "").trim();
355             String cloneDefaultTextualAbbreviation = siPrefix.getDefaultTextualPrefix()
356                     + clone.getDefaultTextualAbbreviation().replace("1/", "").replace("/", "").trim();
357 
358             // Make a builder and set values
359             Builder<U> builder = makeBuilder();
360             builder.setId(cloneId);
361             builder.setName(cloneName);
362             builder.setQuantity(this.quantity);
363             builder.setSiPrefixes(SIPrefixes.NONE, 1.0);
364             builder.setDefaultDisplayAbbreviation(cloneDefaultAbbreviation);
365             builder.setDefaultTextualAbbreviation(cloneDefaultTextualAbbreviation);
366             builder.setAdditionalAbbreviations(cloneAbbreviations.toArray(new String[cloneAbbreviations.size()]));
367             if (getScale() instanceof OffsetLinearScale)
368             {
369                 builder.setScale(new OffsetLinearScale(
370                         (siPrefixPower == 1.0 ? siPrefix.getFactor() : Math.pow(siPrefix.getFactor(), siPrefixPower))
371                                 * ((LinearScale) getScale()).getConversionFactorToStandardUnit(),
372                         0.0));
373             }
374             else
375             {
376                 builder.setScale(new LinearScale(
377                         (siPrefixPower == 1.0 ? siPrefix.getFactor() : Math.pow(siPrefix.getFactor(), siPrefixPower))
378                                 * ((LinearScale) getScale()).getConversionFactorToStandardUnit()));
379             }
380             builder.setUnitSystem(this.unitSystem); // SI_BASE stays SI_BASE with prefix
381             builder.setGenerated(automaticallyGenerated);
382 
383             return clone.build(builder);
384         }
385         catch (CloneNotSupportedException exception)
386         {
387             throw new UnitRuntimeException(exception);
388         }
389     }
390 
391     /**
392      * Create a linearly scaled version of this unit. The scale field will be filled with the correct scaleFactor. Note that the
393      * unit that is used for derivation can already have a scaleFactor.
394      * @param scaleFactor double; the linear scale factor of the unit
395      * @param derivedId String; the new id of the derived unit
396      * @param derivedName String; the new name of the derived unit
397      * @param derivedUnitSystem UnitSystem; the unit system of the derived unit
398      * @param derivedDefaultDisplayAbbreviation String; the default abbreviation to use in e.g, the toString() method. Can be
399      *            null.
400      * @param derivedDefaultTextualAbbreviation String; the default textual abbreviation to use in, e.g, typing. Can be null.
401      * @param derivedAbbreviations String...; the other valid abbreviations for the unit, e.g. {"h", "hr", "hour"}. Can be left
402      *            out.
403      * @return U; a linearly scaled instance of this unit with new id, abbreviation, name, and unit system
404      * @throws UnitRuntimeException when cloning fails
405      */
406     public U deriveLinear(final double scaleFactor, final String derivedId, final String derivedName,
407             final UnitSystem derivedUnitSystem, final String derivedDefaultDisplayAbbreviation,
408             final String derivedDefaultTextualAbbreviation, final String... derivedAbbreviations)
409     {
410         String cName = getClass().getSimpleName();
411         Throw.whenNull(derivedId, "deriving unit from %s.%s; derivedId cannot be null", cName, this.id);
412         Throw.whenNull(derivedName, "deriving unit from %s.%s; derivedName cannot be null", cName, this.id);
413         Throw.whenNull(derivedUnitSystem, "deriving unit from %s.%s; derivedUnitSystem cannot be null", cName, this.id);
414         if (!getScale().getClass().equals(LinearScale.class) && !getScale().getClass().equals(IdentityScale.class))
415         {
416             throw new UnitRuntimeException("Cannot derive from unit " + cName + "." + getId() + " with scale "
417                     + getScale().getClass().getSimpleName() + ". Scale should be Linear");
418         }
419 
420         try
421         {
422             U clone = clone();
423 
424             // make a builder and set values
425             Builder<U> builder = makeBuilder();
426             builder.setId(derivedId);
427             builder.setName(derivedName);
428             builder.setQuantity(this.quantity);
429             builder.setSiPrefixes(SIPrefixes.NONE, 1.0);
430             builder.setScale(new LinearScale(scaleFactor * ((LinearScale) getScale()).getConversionFactorToStandardUnit()));
431             builder.setUnitSystem(derivedUnitSystem);
432             builder.setDefaultDisplayAbbreviation(derivedDefaultDisplayAbbreviation);
433             builder.setDefaultTextualAbbreviation(derivedDefaultTextualAbbreviation);
434             if (derivedAbbreviations != null)
435             {
436                 builder.setAdditionalAbbreviations(derivedAbbreviations);
437             }
438 
439             return clone.build(builder);
440         }
441         catch (CloneNotSupportedException exception)
442         {
443             throw new UnitRuntimeException(exception);
444         }
445     }
446 
447     /**
448      * Create a linearly scaled version of this unit. The scale field will be filled with the correct scaleFactor. Note that the
449      * unit that is used for derivation can already have a scaleFactor.
450      * @param scaleFactor double; the linear scale factor of the unit
451      * @param derivedId String; the new id of the derived unit
452      * @param derivedName String; the new name of the derived unit
453      * @param derivedUnitSystem UnitSystem; the unit system of the derived unit
454      * @return U; a linearly scaled instance of this unit with new id, abbreviation, name, and unit system
455      * @throws UnitRuntimeException when cloning fails
456      */
457     public U deriveLinear(final double scaleFactor, final String derivedId, final String derivedName,
458             final UnitSystem derivedUnitSystem)
459     {
460         return deriveLinear(scaleFactor, derivedId, derivedName, derivedUnitSystem, null, null);
461     }
462 
463     /**
464      * Create a linearly scaled version of this unit. The unitSystem will be copied. The scale field will be filled with the
465      * correct scaleFactor. Note that the unit that is used for derivation can already have a scaleFactor.
466      * @param scaleFactor double; the linear scale factor of the unit
467      * @param derivedId String; the new id of the derived unit
468      * @param derivedName String; the new name of the derived unit
469      * @return U; a linearly scaled instance of this unit with new id, abbreviation, name, and unit system
470      * @throws UnitRuntimeException when cloning fails
471      */
472     public U deriveLinear(final double scaleFactor, final String derivedId, final String derivedName)
473     {
474         return deriveLinear(scaleFactor, derivedId, derivedName, getUnitSystem());
475     }
476 
477     /**
478      * Create a Builder. Override at subclasses to create extended builder.
479      * @return an instance of a builder.
480      */
481     public Builder<U> makeBuilder()
482     {
483         return new Builder<U>();
484     }
485 
486     /**
487      * Create or lookup a unit based on given SI dimensions. E.g., a unit with dimensions 1/s^2 or kg.m/s^2.
488      * @param siDimensions SIDimensions; the vector with the dimensionality of the unit
489      * @return SIUnit; an SIUnit object with the right dimensions
490      */
491     @SuppressWarnings("unchecked")
492     public static SIUnit lookupOrCreateUnitWithSIDimensions(final SIDimensions siDimensions)
493     {
494         Throw.whenNull(siDimensions, "siDimensions cannot be null");
495 
496         Quantity<SIUnit> quantity = null;
497         SIUnit unit = null;
498 
499         Set<Quantity<?>> baseUnitSet = Quantities.INSTANCE.getQuantities(siDimensions);
500         for (Quantity<?> bu : baseUnitSet)
501         {
502             if (bu.getStandardUnit().getClass().equals(Unit.class))
503             {
504                 quantity = (Quantity<SIUnit>) bu;
505             }
506         }
507 
508         if (quantity == null)
509         {
510             quantity = new Quantity<SIUnit>(siDimensions.toString(), siDimensions);
511             Builder<SIUnit> builder = new Builder<>();
512             builder.setId(siDimensions.toString(true, true));
513             builder.setName(siDimensions.toString(true, true));
514             builder.setQuantity(quantity);
515             builder.setScale(IdentityScale.SCALE);
516             builder.setGenerated(true);
517             builder.setUnitSystem(UnitSystem.SI_DERIVED);
518             builder.setSiPrefixes(SIPrefixes.NONE, 1.0);
519             unit = new SIUnit();
520             unit.build(builder); // it will be registered at the BaseUnit
521         }
522         else
523         {
524             unit = quantity.getStandardUnit();
525         }
526 
527         return unit;
528     }
529 
530     /**
531      * Retrieve the unit id.
532      * @return String; the unit id
533      */
534     public String getId()
535     {
536         return this.id;
537     }
538 
539     /**
540      * Retrieve a safe copy of the unit abbreviations.
541      * @return Set&lt;String&gt;; the unit abbreviations
542      */
543     public Set<String> getDefaultAbbreviations()
544     {
545         return new LinkedHashSet<>(this.abbreviations);
546     }
547 
548     /**
549      * Retrieve the default abbreviation.
550      * @return String; the default abbreviation
551      */
552     public String getDefaultDisplayAbbreviation()
553     {
554         return this.defaultDisplayAbbreviation;
555     }
556 
557     /**
558      * Retrieve the default textual abbreviation.
559      * @return String; the default textual abbreviation
560      */
561     public String getDefaultTextualAbbreviation()
562     {
563         return this.defaultTextualAbbreviation;
564     }
565 
566     /**
567      * Retrieve the name of this unit.
568      * @return String; the name of this unit
569      */
570     public String getName()
571     {
572         return this.name;
573     }
574 
575     /**
576      * Retrieve a safe copy of the localized unit abbreviations.
577      * @return Set&lt;String&gt;; the localized unit abbreviations
578      */
579     @SuppressWarnings("unchecked")
580     public Set<String> getLocalizedAbbreviations()
581     {
582         return getQuantity().getLocalizedAbbreviations((U) this);
583     }
584 
585     /**
586      * Retrieve the localized display abbreviation.
587      * @return String; the localized display abbreviation
588      */
589     @SuppressWarnings("unchecked")
590     public String getLocalizedDisplayAbbreviation()
591     {
592         return getQuantity().getLocalizedDisplayAbbreviation((U) this);
593     }
594 
595     /**
596      * Retrieve the localized textual abbreviation.
597      * @return String; the localized textual abbreviation
598      */
599     @SuppressWarnings("unchecked")
600     public String getLocalizedTextualAbbreviation()
601     {
602         return getQuantity().getLocalizedTextualAbbreviation((U) this);
603     }
604 
605     /**
606      * Retrieve the scale of this unit.
607      * @return Scale; the scale of this unit
608      */
609     public Scale getScale()
610     {
611         return this.scale;
612     }
613 
614     /**
615      * Retrieve the unit system of this unit.
616      * @return unitSystem the unit system of this unit
617      */
618     public UnitSystem getUnitSystem()
619     {
620         return this.unitSystem;
621     }
622 
623     /**
624      * Retrieve the unit base of this unit.
625      * @return BaseUnit&lt;U&gt;; the unit base of this unit. if this unit is itself a unit base; the returned value is
626      *         <code>null</code>
627      */
628     public Quantity<U> getQuantity()
629     {
630         return this.quantity;
631     }
632 
633     /**
634      * Indicate whether is unit was automatically generated.
635      * @return boolean; true if this unit has been automatically generate; false if it was not automatically generated
636      */
637     public boolean isGenerated()
638     {
639         return this.generated;
640     }
641 
642     /**
643      * Indicate whether this unit has the standard SI signature.
644      * @return boolean; true if this unit has the standard SI signature; false if this unit does not have the standard SI
645      *         signature
646      */
647     public boolean isBaseSIUnit()
648     {
649         return this.baseSIUnit;
650     }
651 
652     /**
653      * Retrieve the standard unit (SI Unit) belonging to this unit.
654      * @return U; the standard unit (SI unit) belonging to this unit
655      */
656     public U getStandardUnit()
657     {
658         return getQuantity().getStandardUnit();
659     }
660 
661     @SuppressWarnings("unchecked")
662     @Override
663     public U clone() throws CloneNotSupportedException
664     {
665         return (U) super.clone();
666     }
667 
668     @Override
669     public int hashCode()
670     {
671         final int prime = 31;
672         int result = 1;
673         result = prime * result + ((this.abbreviations == null) ? 0 : this.abbreviations.hashCode());
674         result = prime * result + ((this.quantity == null) ? 0 : this.quantity.hashCode());
675         result = prime * result + ((this.defaultDisplayAbbreviation == null) ? 0 : this.defaultDisplayAbbreviation.hashCode());
676         result = prime * result + ((this.defaultTextualAbbreviation == null) ? 0 : this.defaultTextualAbbreviation.hashCode());
677         result = prime * result + (this.generated ? 1231 : 1237);
678         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
679         result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
680         result = prime * result + ((this.scale == null) ? 0 : this.scale.hashCode());
681         result = prime * result + ((this.unitSystem == null) ? 0 : this.unitSystem.hashCode());
682         return result;
683     }
684 
685     @Override
686     @SuppressWarnings("checkstyle:needbraces")
687     public boolean equals(final Object obj)
688     {
689         if (this == obj)
690             return true;
691         if (obj == null)
692             return false;
693         if (getClass() != obj.getClass())
694             return false;
695         Unit<?> other = (Unit<?>) obj;
696         if (this.abbreviations == null)
697         {
698             if (other.abbreviations != null)
699                 return false;
700         }
701         else if (!this.abbreviations.equals(other.abbreviations))
702             return false;
703         if (this.quantity == null)
704         {
705             if (other.quantity != null)
706                 return false;
707         }
708         else if (!this.quantity.equals(other.quantity))
709             return false;
710         if (this.defaultDisplayAbbreviation == null)
711         {
712             if (other.defaultDisplayAbbreviation != null)
713                 return false;
714         }
715         else if (!this.defaultDisplayAbbreviation.equals(other.defaultDisplayAbbreviation))
716             return false;
717         if (this.defaultTextualAbbreviation == null)
718         {
719             if (other.defaultTextualAbbreviation != null)
720                 return false;
721         }
722         else if (!this.defaultTextualAbbreviation.equals(other.defaultTextualAbbreviation))
723             return false;
724         if (this.generated != other.generated)
725             return false;
726         if (this.id == null)
727         {
728             if (other.id != null)
729                 return false;
730         }
731         else if (!this.id.equals(other.id))
732             return false;
733         if (this.name == null)
734         {
735             if (other.name != null)
736                 return false;
737         }
738         else if (!this.name.equals(other.name))
739             return false;
740         if (this.scale == null)
741         {
742             if (other.scale != null)
743                 return false;
744         }
745         else if (!this.scale.equals(other.scale))
746             return false;
747         if (this.unitSystem == null)
748         {
749             if (other.unitSystem != null)
750                 return false;
751         }
752         else if (!this.unitSystem.equals(other.unitSystem))
753             return false;
754         return true;
755     }
756 
757     @Override
758     public String toString()
759     {
760         return this.defaultDisplayAbbreviation;
761     }
762 
763     /**
764      * The class that contains the information to build a unit.
765      * <p>
766      * Copyright (c) 2019-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
767      * <br>
768      * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
769      * </p>
770      * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
771      * @param <U> the unit for which the builder contains the information.
772      */
773     public static class Builder<U extends Unit<U>> implements Serializable
774     {
775         /** ... */
776         private static final long serialVersionUID = 20191018L;
777 
778         /** The id of the unit; has to be unique within the unit name. Used for, e.g., localization and retrieval. */
779         private String id;
780 
781         /** The abbreviations in the default locale. All abbreviations an be used in the valueOf() and of() methods. */
782         private Set<String> additionalAbbreviations = new LinkedHashSet<>();
783 
784         /** The default abbreviation in the default locale for printing. Included in the abbreviations list. */
785         private String defaultDisplayAbbreviation;
786 
787         /** The default textual abbreviation in the default locale for data entry. Included in the abbreviations list. */
788         private String defaultTextualAbbreviation;
789 
790         /** The full name of the unit in the default locale. */
791         private String name;
792 
793         /** The scale to use to convert between this unit and the standard (e.g., SI, BASE) unit. */
794         private Scale scale;
795 
796         /** The unit system, e.g. SI or Imperial. */
797         private UnitSystem unitSystem;
798 
799         /** Whether or not the unit supports SI prefixes from "yotta" (y) to "yocto" (Y). */
800         private SIPrefixes siPrefixes;
801 
802         /** The power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters. */
803         private double siPrefixPower = 1.0;
804 
805         /** Whether the unit has been automatically generated or not. */
806         private boolean generated = false;
807 
808         /**
809          * The corresponding unit base that contains all registered units for the unit as well as SI dimension information. The
810          * unit base should never be null.
811          */
812         private Quantity<U> quantity;
813 
814         /**
815          * Empty constructor. Content is generated through chaining: new
816          * Unit.Builder&lt;TypeUnit&gt;().setId("id").setName("name");
817          */
818         public Builder()
819         {
820             // Empty constructor. Content is generated through (chaining of) method calls
821         }
822 
823         /**
824          * Return whether SI prefixes, ranging from yotta (y) to yocto (Y), will be generated.
825          * @return siPrefixes, NONE (e.g., for inch), ALL (e.g., for meter) or KILO (e.g., for kilometer)
826          */
827         public SIPrefixes getSiPrefixes()
828         {
829             return this.siPrefixes;
830         }
831 
832         /**
833          * Return the power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters.
834          * @return siPrefixPower double; power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters
835          */
836         public double getSiPrefixPowerFactor()
837         {
838             return this.siPrefixPower;
839         }
840 
841         /**
842          * Set whether SI prefixes, ranging from yotta (y) to yocto (Y), are allowed. If not set; this property defaults to
843          * <code>false</code>.
844          * @param newSiPrefixes SIPrefixes; SIPrefixes set siPrefixes, NONE (e.g., for inch), ALL (e.g., for meter) or KILO
845          *            (e.g., for kilometer)
846          * @param power double; power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters
847          * @return Builder; this builder instance that is being constructed (for method call chaining)
848          */
849         public Builder<U> setSiPrefixes(final SIPrefixes newSiPrefixes, final double power)
850         {
851             this.siPrefixes = newSiPrefixes;
852             this.siPrefixPower = power;
853             return this;
854         }
855 
856         /**
857          * Retrieve the id of the unit that this builder builds.
858          * @return String; the id of the unit that this builder builds
859          */
860         public String getId()
861         {
862             return this.id;
863         }
864 
865         /**
866          * Set the id of the unit that this builder builds.
867          * @param newId String; set the id of the unit that this builder builds (must be set; the default is <code>null</code>
868          *            which is invalid)
869          * @return Builder; this builder instance that is being constructed (for method call chaining)
870          */
871         public Builder<U> setId(final String newId)
872         {
873             this.id = newId;
874             return this;
875         }
876 
877         /**
878          * Retrieve the additional abbreviations.
879          * @return Set&lt;String&gt;; the additional abbreviations
880          */
881         public Set<String> getAdditionalAbbreviations()
882         {
883             return this.additionalAbbreviations;
884         }
885 
886         /**
887          * Set the additional abbreviations.
888          * @param newAdditionalAbbreviations String...; the additional abbreviations
889          * @return Builder; this builder instance that is being constructed (for method call chaining)
890          */
891         public Builder<U> setAdditionalAbbreviations(final String... newAdditionalAbbreviations)
892         {
893             this.additionalAbbreviations = new LinkedHashSet<>(Arrays.asList(newAdditionalAbbreviations)); // safe copy
894             return this;
895         }
896 
897         /**
898          * Retrieve the default display abbreviation.
899          * @return String; the default display abbreviation
900          */
901         public String getDefaultDisplayAbbreviation()
902         {
903             return this.defaultDisplayAbbreviation;
904         }
905 
906         /**
907          * Set the default display abbreviation.
908          * @param newDefaultDisplayAbbreviation String; the default display abbreviation
909          * @return Builder; this builder instance that is being constructed (for method call chaining)
910          */
911         public Builder<U> setDefaultDisplayAbbreviation(final String newDefaultDisplayAbbreviation)
912         {
913             this.defaultDisplayAbbreviation = newDefaultDisplayAbbreviation;
914             return this;
915         }
916 
917         /**
918          * Retrieve the default textual abbreviation.
919          * @return String; the default textual abbreviation
920          */
921         public String getDefaultTextualAbbreviation()
922         {
923             return this.defaultTextualAbbreviation;
924         }
925 
926         /**
927          * Set the default textual abbreviation.
928          * @param newDefaultTextualAbbreviation String; the default textual abbreviation
929          * @return Builder; this builder instance that is being constructed (for method call chaining)
930          */
931         public Builder<U> setDefaultTextualAbbreviation(final String newDefaultTextualAbbreviation)
932         {
933             this.defaultTextualAbbreviation = newDefaultTextualAbbreviation;
934             return this;
935         }
936 
937         /**
938          * Return the name.
939          * @return String; the name
940          */
941         public String getName()
942         {
943             return this.name;
944         }
945 
946         /**
947          * Set the name.
948          * @param newName String; the name
949          * @return Builder; this builder instance that is being constructed (for method call chaining)
950          */
951         public Builder<U> setName(final String newName)
952         {
953             this.name = newName;
954             return this;
955         }
956 
957         /**
958          * Retrieve the scale.
959          * @return Scale; the scale
960          */
961         public Scale getScale()
962         {
963             return this.scale;
964         }
965 
966         /**
967          * Set the scale.
968          * @param newScale Scale; set the scale
969          * @return Builder; this builder instance that is being constructed (for method call chaining)
970          */
971         public Builder<U> setScale(final Scale newScale)
972         {
973             this.scale = newScale;
974             return this;
975         }
976 
977         /**
978          * Retrieve the unit system.
979          * @return unitSystem UnitSystem; the unit system
980          */
981         public UnitSystem getUnitSystem()
982         {
983             return this.unitSystem;
984         }
985 
986         /**
987          * Set the unit system.
988          * @param newUnitSystem UnitSystem; the unit system
989          * @return Builder; this builder instance that is being constructed (for method call chaining)
990          */
991         public Builder<U> setUnitSystem(final UnitSystem newUnitSystem)
992         {
993             this.unitSystem = newUnitSystem;
994             return this;
995         }
996 
997         /**
998          * Retrieve the generated flag.
999          * @return generated Boolean; the generated flag
1000          */
1001         public boolean isGenerated()
1002         {
1003             return this.generated;
1004         }
1005 
1006         /**
1007          * Set the generated flag. Defaults to false. Should be set for units that are automatically generated.
1008          * @param newGenerated boolean; the value for the generated flag
1009          * @return Builder; this builder instance that is being constructed (for method call chaining)
1010          */
1011         public Builder<U> setGenerated(final boolean newGenerated)
1012         {
1013             this.generated = newGenerated;
1014             return this;
1015         }
1016 
1017         /**
1018          * Retrieve the unit base.
1019          * @return baseUnit BaseUnit&lt;U&gt;; the unit base
1020          */
1021         public Quantity<U> getQuantity()
1022         {
1023             return this.quantity;
1024         }
1025 
1026         /**
1027          * Set the unit base. Can never be null and has to be filled.
1028          * @param newQuantity Quantity&lt;U&gt;; the unit base
1029          * @return Builder; this builder instance that is being constructed (for method call chaining)
1030          */
1031         public Builder<U> setQuantity(final Quantity<U> newQuantity)
1032         {
1033             this.quantity = newQuantity;
1034             return this;
1035         }
1036 
1037         @Override
1038         public String toString()
1039         {
1040             return "Builder [id=" + this.id + ", name=" + this.name + ", scale=" + this.scale + "]";
1041         }
1042 
1043     }
1044 
1045     /**
1046      * Find or create a unit for the given SI dimensions. Note that unitString may be empty, corresponding to a Dimensionless
1047      * unit.
1048      * @param unitString String; the textual representation of the unit
1049      * @return SIUnit; the unit
1050      * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
1051      * @throws NullPointerException when the unitString argument is null
1052      */
1053     public static SIUnit getUnit(final String unitString)
1054     {
1055         Throw.whenNull(unitString, "Error parsing SIVector: unitString is null");
1056         try
1057         {
1058             SIUnit unit = Unit.lookupOrCreateUnitWithSIDimensions(SIDimensions.of(unitString));
1059             if (unit != null)
1060             {
1061                 return unit;
1062             }
1063         }
1064         catch (Exception exception)
1065         {
1066             throw new IllegalArgumentException("Error parsing SIUnit from " + unitString, exception);
1067         }
1068         throw new IllegalArgumentException("Error parsing SIVector with unit " + unitString);
1069     }
1070 
1071 }