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