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.Throw;
9 import org.djunits.unit.quantity.Quantities;
10 import org.djunits.unit.quantity.Quantity;
11 import org.djunits.unit.scale.IdentityScale;
12 import org.djunits.unit.scale.LinearScale;
13 import org.djunits.unit.scale.OffsetLinearScale;
14 import org.djunits.unit.scale.Scale;
15 import org.djunits.unit.si.SIDimensions;
16 import org.djunits.unit.si.SIPrefix;
17 import org.djunits.unit.si.SIPrefixes;
18 import org.djunits.unit.unitsystem.UnitSystem;
19 import org.djunits.unit.util.UnitRuntimeException;
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-2022 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<U>; Builder<U> 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.getAbbreviations())
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 : getAbbreviations())
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.getAbbreviations())
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.getAbbreviations())
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 LinearScaleg/djunits/unit/scale/LinearScale.html#LinearScale">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<String>; the unit abbreviations
534 */
535 public Set<String> getAbbreviations()
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 the scale of this unit.
569 * @return Scale; the scale of this unit
570 */
571 public Scale getScale()
572 {
573 return this.scale;
574 }
575
576 /**
577 * Retrieve the unit system of this unit.
578 * @return unitSystem the unit system of this unit
579 */
580 public UnitSystem getUnitSystem()
581 {
582 return this.unitSystem;
583 }
584
585 /**
586 * Retrieve the unit base of this unit.
587 * @return BaseUnit<U>; the unit base of this unit. if this unit is itself a unit base; the returned value is
588 * <code>null</code>
589 */
590 public Quantity<U> getQuantity()
591 {
592 return this.quantity;
593 }
594
595 /**
596 * Indicate whether is unit was automatically generated.
597 * @return boolean; true if this unit has been automatically generate; false if it was not automatically generated
598 */
599 public boolean isGenerated()
600 {
601 return this.generated;
602 }
603
604 /**
605 * Indicate whether this unit has the standard SI signature.
606 * @return boolean; true if this unit has the standard SI signature; false if this unit does not have the standard SI
607 * signature
608 */
609 public boolean isBaseSIUnit()
610 {
611 return this.baseSIUnit;
612 }
613
614 /**
615 * Retrieve the standard unit (SI Unit) belonging to this unit.
616 * @return U; the standard unit (SI unit) belonging to this unit
617 */
618 public U getStandardUnit()
619 {
620 return getQuantity().getStandardUnit();
621 }
622
623 /** {@inheritDoc} */
624 @SuppressWarnings("unchecked")
625 @Override
626 public U clone() throws CloneNotSupportedException
627 {
628 return (U) super.clone();
629 }
630
631 /** {@inheritDoc} */
632 @Override
633 public int hashCode()
634 {
635 final int prime = 31;
636 int result = 1;
637 result = prime * result + ((this.abbreviations == null) ? 0 : this.abbreviations.hashCode());
638 result = prime * result + ((this.quantity == null) ? 0 : this.quantity.hashCode());
639 result = prime * result + ((this.defaultDisplayAbbreviation == null) ? 0 : this.defaultDisplayAbbreviation.hashCode());
640 result = prime * result + ((this.defaultTextualAbbreviation == null) ? 0 : this.defaultTextualAbbreviation.hashCode());
641 result = prime * result + (this.generated ? 1231 : 1237);
642 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
643 result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
644 result = prime * result + ((this.scale == null) ? 0 : this.scale.hashCode());
645 result = prime * result + ((this.unitSystem == null) ? 0 : this.unitSystem.hashCode());
646 return result;
647 }
648
649 /** {@inheritDoc} */
650 @Override
651 @SuppressWarnings("checkstyle:needbraces")
652 public boolean equals(final Object obj)
653 {
654 if (this == obj)
655 return true;
656 if (obj == null)
657 return false;
658 if (getClass() != obj.getClass())
659 return false;
660 Unit<?> other = (Unit<?>) obj;
661 if (this.abbreviations == null)
662 {
663 if (other.abbreviations != null)
664 return false;
665 }
666 else if (!this.abbreviations.equals(other.abbreviations))
667 return false;
668 if (this.quantity == null)
669 {
670 if (other.quantity != null)
671 return false;
672 }
673 else if (!this.quantity.equals(other.quantity))
674 return false;
675 if (this.defaultDisplayAbbreviation == null)
676 {
677 if (other.defaultDisplayAbbreviation != null)
678 return false;
679 }
680 else if (!this.defaultDisplayAbbreviation.equals(other.defaultDisplayAbbreviation))
681 return false;
682 if (this.defaultTextualAbbreviation == null)
683 {
684 if (other.defaultTextualAbbreviation != null)
685 return false;
686 }
687 else if (!this.defaultTextualAbbreviation.equals(other.defaultTextualAbbreviation))
688 return false;
689 if (this.generated != other.generated)
690 return false;
691 if (this.id == null)
692 {
693 if (other.id != null)
694 return false;
695 }
696 else if (!this.id.equals(other.id))
697 return false;
698 if (this.name == null)
699 {
700 if (other.name != null)
701 return false;
702 }
703 else if (!this.name.equals(other.name))
704 return false;
705 if (this.scale == null)
706 {
707 if (other.scale != null)
708 return false;
709 }
710 else if (!this.scale.equals(other.scale))
711 return false;
712 if (this.unitSystem == null)
713 {
714 if (other.unitSystem != null)
715 return false;
716 }
717 else if (!this.unitSystem.equals(other.unitSystem))
718 return false;
719 return true;
720 }
721
722 /** {@inheritDoc} */
723 @Override
724 public String toString()
725 {
726 return this.defaultDisplayAbbreviation;
727 }
728
729 /**
730 * The class that contains the information to build a unit.
731 * <p>
732 * Copyright (c) 2019-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
733 * <br>
734 * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
735 * <p>
736 * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
737 * @param <U> the unit for which the builder contains the information.
738 */
739 public static class Builder<U extends Unit<U>> implements Serializable
740 {
741 /** ... */
742 private static final long serialVersionUID = 20191018L;
743
744 /** The id of the unit; has to be unique within the unit name. Used for, e.g., localization and retrieval. */
745 private String id;
746
747 /** The abbreviations in the default locale. All abbreviations an be used in the valueOf() and of() methods. */
748 private Set<String> additionalAbbreviations = new LinkedHashSet<>();
749
750 /** The default abbreviation in the default locale for printing. Included in the abbreviations list. */
751 private String defaultDisplayAbbreviation;
752
753 /** The default textual abbreviation in the default locale for data entry. Included in the abbreviations list. */
754 private String defaultTextualAbbreviation;
755
756 /** The full name of the unit in the default locale. */
757 private String name;
758
759 /** The scale to use to convert between this unit and the standard (e.g., SI, BASE) unit. */
760 private Scale scale;
761
762 /** The unit system, e.g. SI or Imperial. */
763 private UnitSystem unitSystem;
764
765 /** Whether or not the unit supports SI prefixes from "yotta" (y) to "yocto" (Y). */
766 private SIPrefixes siPrefixes;
767
768 /** The power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters. */
769 private double siPrefixPower = 1.0;
770
771 /** Whether the unit has been automatically generated or not. */
772 private boolean generated = false;
773
774 /**
775 * The corresponding unit base that contains all registered units for the unit as well as SI dimension information. The
776 * unit base should never be null.
777 */
778 private Quantity<U> quantity;
779
780 /**
781 * Empty constructor. Content is generated through chaining: new
782 * Unit.Builder<TypeUnit>().setId("id").setName("name");
783 */
784 public Builder()
785 {
786 // Empty constructor. Content is generated through (chaining of) method calls
787 }
788
789 /**
790 * Return whether SI prefixes, ranging from yotta (y) to yocto (Y), will be generated.
791 * @return siPrefixes, NONE (e.g., for inch), ALL (e.g., for meter) or KILO (e.g., for kilometer)
792 */
793 public SIPrefixes getSiPrefixes()
794 {
795 return this.siPrefixes;
796 }
797
798 /**
799 * Return the power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters.
800 * @return siPrefixPower double; power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters
801 */
802 public double getSiPrefixPowerFactor()
803 {
804 return this.siPrefixPower;
805 }
806
807 /**
808 * Set whether SI prefixes, ranging from yotta (y) to yocto (Y), are allowed. If not set; this property defaults to
809 * <code>false</code>.
810 * @param newSiPrefixes SIPrefixes; SIPrefixes set siPrefixes, NONE (e.g., for inch), ALL (e.g., for meter) or KILO
811 * (e.g., for kilometer)
812 * @param power double; power factor of the SI prefixes, e.g. 2.0 for square meters and 3.0 for cubic meters
813 * @return Builder; this builder instance that is being constructed (for method call chaining)
814 */
815 public Builder<U> setSiPrefixes(final SIPrefixes newSiPrefixes, final double power)
816 {
817 this.siPrefixes = newSiPrefixes;
818 this.siPrefixPower = power;
819 return this;
820 }
821
822 /**
823 * Retrieve the id of the unit that this builder builds.
824 * @return String; the id of the unit that this builder builds
825 */
826 public String getId()
827 {
828 return this.id;
829 }
830
831 /**
832 * Set the id of the unit that this builder builds.
833 * @param newId String; set the id of the unit that this builder builds (must be set; the default is <code>null</code>
834 * which is invalid)
835 * @return Builder; this builder instance that is being constructed (for method call chaining)
836 */
837 public Builder<U> setId(final String newId)
838 {
839 this.id = newId;
840 return this;
841 }
842
843 /**
844 * Retrieve the additional abbreviations.
845 * @return Set<String>; the additional abbreviations
846 */
847 public Set<String> getAdditionalAbbreviations()
848 {
849 return this.additionalAbbreviations;
850 }
851
852 /**
853 * Set the additional abbreviations.
854 * @param newAdditionalAbbreviations String...; the additional abbreviations
855 * @return Builder; this builder instance that is being constructed (for method call chaining)
856 */
857 public Builder<U> setAdditionalAbbreviations(final String... newAdditionalAbbreviations)
858 {
859 this.additionalAbbreviations = new LinkedHashSet<>(Arrays.asList(newAdditionalAbbreviations)); // safe copy
860 return this;
861 }
862
863 /**
864 * Retrieve the default display abbreviation.
865 * @return String; the default display abbreviation
866 */
867 public String getDefaultDisplayAbbreviation()
868 {
869 return this.defaultDisplayAbbreviation;
870 }
871
872 /**
873 * Set the default display abbreviation.
874 * @param newDefaultDisplayAbbreviation String; the default display abbreviation
875 * @return Builder; this builder instance that is being constructed (for method call chaining)
876 */
877 public Builder<U> setDefaultDisplayAbbreviation(final String newDefaultDisplayAbbreviation)
878 {
879 this.defaultDisplayAbbreviation = newDefaultDisplayAbbreviation;
880 return this;
881 }
882
883 /**
884 * Retrieve the default textual abbreviation.
885 * @return String; the default textual abbreviation
886 */
887 public String getDefaultTextualAbbreviation()
888 {
889 return this.defaultTextualAbbreviation;
890 }
891
892 /**
893 * Set the default textual abbreviation.
894 * @param newDefaultTextualAbbreviation String; the default textual abbreviation
895 * @return Builder; this builder instance that is being constructed (for method call chaining)
896 */
897 public Builder<U> setDefaultTextualAbbreviation(final String newDefaultTextualAbbreviation)
898 {
899 this.defaultTextualAbbreviation = newDefaultTextualAbbreviation;
900 return this;
901 }
902
903 /**
904 * Return the name.
905 * @return String; the name
906 */
907 public String getName()
908 {
909 return this.name;
910 }
911
912 /**
913 * Set the name.
914 * @param newName String; the name
915 * @return Builder; this builder instance that is being constructed (for method call chaining)
916 */
917 public Builder<U> setName(final String newName)
918 {
919 this.name = newName;
920 return this;
921 }
922
923 /**
924 * Retrieve the scale.
925 * @return Scale; the scale
926 */
927 public Scale getScale()
928 {
929 return this.scale;
930 }
931
932 /**
933 * Set the scale.
934 * @param newScale Scale; set the scale
935 * @return Builder; this builder instance that is being constructed (for method call chaining)
936 */
937 public Builder<U> setScale(final Scale newScale)
938 {
939 this.scale = newScale;
940 return this;
941 }
942
943 /**
944 * Retrieve the unit system.
945 * @return unitSystem UnitSystem; the unit system
946 */
947 public UnitSystem getUnitSystem()
948 {
949 return this.unitSystem;
950 }
951
952 /**
953 * Set the unit system.
954 * @param newUnitSystem UnitSystem; the unit system
955 * @return Builder; this builder instance that is being constructed (for method call chaining)
956 */
957 public Builder<U> setUnitSystem(final UnitSystem newUnitSystem)
958 {
959 this.unitSystem = newUnitSystem;
960 return this;
961 }
962
963 /**
964 * Retrieve the generated flag.
965 * @return generated Boolean; the generated flag
966 */
967 public boolean isGenerated()
968 {
969 return this.generated;
970 }
971
972 /**
973 * Set the generated flag. Defaults to false. Should be set for units that are automatically generated.
974 * @param newGenerated boolean; the value for the generated flag
975 * @return Builder; this builder instance that is being constructed (for method call chaining)
976 */
977 public Builder<U> setGenerated(final boolean newGenerated)
978 {
979 this.generated = newGenerated;
980 return this;
981 }
982
983 /**
984 * Retrieve the unit base.
985 * @return baseUnit BaseUnit<U>; the unit base
986 */
987 public Quantity<U> getQuantity()
988 {
989 return this.quantity;
990 }
991
992 /**
993 * Set the unit base. Can never be null and has to be filled.
994 * @param newQuantity Quantity<U>; the unit base
995 * @return Builder; this builder instance that is being constructed (for method call chaining)
996 */
997 public Builder<U> setQuantity(final Quantity<U> newQuantity)
998 {
999 this.quantity = newQuantity;
1000 return this;
1001 }
1002
1003 /** {@inheritDoc} */
1004 @Override
1005 public String toString()
1006 {
1007 return "Builder [id=" + this.id + ", name=" + this.name + ", scale=" + this.scale + "]";
1008 }
1009
1010 }
1011
1012 /**
1013 * Find or create a unit for the given SI dimensions.
1014 * @param unitString String; the textual representation of the unit
1015 * @return SIUnit; the unit
1016 * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
1017 * @throws NullPointerException when the unitString argument is null
1018 */
1019 public static SIUnit getUnit(final String unitString)
1020 {
1021 Throw.whenNull(unitString, "Error parsing SIVector: unitString is null");
1022 Throw.when(unitString.length() == 0, IllegalArgumentException.class, "Error parsing SIVector: empty unitString");
1023 try
1024 {
1025 SIUnit unit = Unit.lookupOrCreateUnitWithSIDimensions(SIDimensions.of(unitString));
1026 if (unit != null)
1027 {
1028 return unit;
1029 }
1030 }
1031 catch (Exception exception)
1032 {
1033 throw new IllegalArgumentException("Error parsing SIUnit from " + unitString, exception);
1034 }
1035 throw new IllegalArgumentException("Error parsing SIVector with unit " + unitString);
1036 }
1037
1038 }