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