View Javadoc
1   package org.djunits.quantity.def;
2   
3   import java.util.Locale;
4   import java.util.Objects;
5   
6   import org.djunits.formatter.QuantityFormat;
7   import org.djunits.formatter.QuantityFormatter;
8   import org.djunits.quantity.Area;
9   import org.djunits.quantity.Length;
10  import org.djunits.quantity.SIQuantity;
11  import org.djunits.quantity.Speed;
12  import org.djunits.unit.UnitInterface;
13  import org.djunits.unit.Unitless;
14  import org.djunits.unit.Units;
15  import org.djunits.unit.si.SIUnit;
16  import org.djunits.value.Additive;
17  import org.djunits.value.Scalable;
18  import org.djunits.value.Value;
19  import org.djutils.base.NumberParser;
20  import org.djutils.exceptions.Throw;
21  
22  /**
23   * Quantity is an abstract class that stores the basic information about a quantity. A physical quantity can be expressed as a
24   * value, which is the combination of a numerical value and a unit of measurement. The type of physical quantity is encoded in
25   * the class ({@link Length}, {@link Speed}, {@link Area}, etc.) with its associated (base) unit of measurement, whereas the
26   * numerical value is stored in the si field. Additionally, each quantity has a displayUnit that gives the preference for the
27   * (scaled) display of the quantity, e.g., in a toString() method.
28   * <p>
29   * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
30   * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
31   * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
32   * @author Alexander Verbraeck
33   * @param <Q> the quantity type
34   */
35  public abstract class Quantity<Q extends Quantity<Q>> extends Number
36          implements Value<Q>, Comparable<Q>, Additive<Q>, Scalable<Q>
37  {
38      /** */
39      private static final long serialVersionUID = 600L;
40  
41      /** The si value. */
42      @SuppressWarnings("checkstyle:visibilitymodifier")
43      public final double si;
44  
45      /** The display unit. */
46      private final UnitInterface<Q> displayUnit;
47  
48      /**
49       * Instantiate a quantity with an SI or base value and a display unit.
50       * @param siValue the quantity value expressed in the SI or base unit
51       * @param displayUnit the display unit to use
52       * @param useSi use SI value when true, value in unit when false
53       */
54      public Quantity(final double siValue, final UnitInterface<Q> displayUnit, final boolean useSi)
55      {
56          Throw.whenNull(displayUnit, "displayUnit");
57          this.si = useSi ? siValue : displayUnit.toBaseValue(siValue);
58          this.displayUnit = displayUnit;
59      }
60  
61      /**********************************************************************************/
62      /******************************* UNIT-RELATED METHODS *****************************/
63      /**********************************************************************************/
64  
65      @Override
66      public UnitInterface<Q> getDisplayUnit()
67      {
68          return this.displayUnit;
69      }
70  
71      /**
72       * Retrieve the value in the current display unit.
73       * @return the value in the current display unit
74       */
75      public double getInUnit()
76      {
77          return getDisplayUnit().getScale().fromIdentityScale(si());
78      }
79  
80      /**
81       * Retrieve the value converted into some specified unit.
82       * @param targetUnit the unit to convert the value into
83       * @return the double value of this quantity expressed in the target unit
84       */
85      public double getInUnit(final UnitInterface<Q> targetUnit)
86      {
87          return targetUnit.getScale().fromIdentityScale(si());
88      }
89  
90      /**
91       * Return the "pretty" name of the quantity.
92       * @return the "pretty" name of the quantity
93       */
94      public String getName()
95      {
96          String name = Units.localizedQuantityName(Locale.getDefault(), getClass().getSimpleName());
97          final StringBuilder sb = new StringBuilder(name.length() + 8);
98          sb.append(name.charAt(0)); // keep first character exactly as-is
99          for (int i = 1; i < name.length(); i++)
100         {
101             final char c = name.charAt(i);
102             if (Character.isUpperCase(c))
103             {
104                 if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ')
105                 {
106                     sb.append(' ');
107                 }
108                 sb.append(Character.toLowerCase(c));
109             }
110             else
111             {
112                 sb.append(c);
113             }
114         }
115         return sb.toString();
116     }
117 
118     /**********************************************************************************/
119     /******************************** SI-RELATED METHODS ******************************/
120     /**********************************************************************************/
121 
122     /**
123      * Return the SI unit of this quantity.
124      * @return the SI unit of this quantity
125      */
126     public SIUnit siUnit()
127     {
128         return getDisplayUnit().siUnit();
129     }
130 
131     /**
132      * Return the SI value of the quantity.
133      * @return the SI value of the quantity
134      */
135     public double si()
136     {
137         return this.si;
138     }
139 
140     /**
141      * Instantiate a quantity with an SI or base value, and a display unit.
142      * @param siValue the value expressed in the base (SI) unit
143      * @param displayUnit the display unit to use for the quantity
144      * @return a quantity with the given SI or base value, and the given display unit
145      */
146     @SuppressWarnings("checkstyle:hiddenfield")
147     public abstract Q instantiateSi(double siValue, UnitInterface<Q> displayUnit);
148 
149     /**
150      * Instantiate a quantity with an SI or base value, with the SI or base display unit.
151      * @param siValue the value expressed in the base (SI) unit
152      * @return a quantity with the given SI or base value, with the SI or base display unit
153      */
154     public Q instantiateSi(final double siValue)
155     {
156         return instantiateSi(siValue, getDisplayUnit().getBaseUnit());
157     }
158 
159     /**
160      * Instantiate a quantity with a value and a unit.
161      * @param valueInUnit the double value, expressed in the unit
162      * @param unit the unit
163      * @return a quantity with the given value and display unit
164      */
165     public Q instantiate(final double valueInUnit, final UnitInterface<Q> unit)
166     {
167         return instantiateSi(unit.toBaseValue(valueInUnit), unit);
168     }
169 
170     /**********************************************************************************/
171     /********************************* NUMBER METHODS *********************************/
172     /**********************************************************************************/
173 
174     @Override
175     public double doubleValue()
176     {
177         return si();
178     }
179 
180     @Override
181     public int intValue()
182     {
183         return (int) Math.round(si());
184     }
185 
186     @Override
187     public long longValue()
188     {
189         return Math.round(si());
190     }
191 
192     @Override
193     public float floatValue()
194     {
195         return (float) si();
196     }
197 
198     /**
199      * Test if this Quantity is less than another Quantity.
200      * @param other the right hand side operand of the comparison
201      * @return true if this is less than o; false otherwise
202      */
203     public boolean lt(final Q other)
204     {
205         return si() < other.si();
206     }
207 
208     /**
209      * Test if this Quantity is less than or equal to another Quantity.
210      * @param other the right hand side operand of the comparison
211      * @return true if this is less than or equal to o; false otherwise
212      */
213     public boolean le(final Q other)
214     {
215         return si() <= other.si();
216     }
217 
218     /**
219      * Test if this Quantity is greater than another Quantity.
220      * @param other the right hand side operand of the comparison
221      * @return true if this is greater than o; false otherwise
222      */
223     public boolean gt(final Q other)
224     {
225         return si() > other.si();
226     }
227 
228     /**
229      * Test if this Quantity is greater than or equal to another Quantity.
230      * @param other the right hand side operand of the comparison
231      * @return true if this is greater than or equal to o; false otherwise
232      */
233     public boolean ge(final Q other)
234     {
235         return si() >= other.si();
236     }
237 
238     /**
239      * Test if this Quantity is equal to another Quantity.
240      * @param other the right hand side operand of the comparison
241      * @return true if this is equal to o; false otherwise
242      */
243     public boolean eq(final Q other)
244     {
245         return si() == other.si();
246     }
247 
248     /**
249      * Test if this Quantity is not equal to another Quantity.
250      * @param other the right hand side operand of the comparison
251      * @return true if this is not equal to o; false otherwise
252      */
253     public boolean ne(final Q other)
254     {
255         return si() != other.si();
256     }
257 
258     /**
259      * Test if this Quantity is less than 0.0.
260      * @return true if this is less than 0.0; false if this is not less than 0.0
261      */
262     public boolean lt0()
263     {
264         return si() < 0.0;
265     }
266 
267     /**
268      * Test if this Quantity is less than or equal to 0.0.
269      * @return true if this is less than or equal to 0.0; false if this is not less than or equal to 0.0
270      */
271     public boolean le0()
272     {
273         return si() <= 0.0;
274     }
275 
276     /**
277      * Test if this Quantity is greater than 0.0.
278      * @return true if this is greater than 0.0; false if this is not greater than 0.0
279      */
280     public boolean gt0()
281     {
282         return si() > 0.0;
283     }
284 
285     /**
286      * Test if this Quantity is greater than or equal to 0.0.
287      * @return true if this is greater than or equal to 0.0; false if this is not greater than or equal to 0.0
288      */
289     public boolean ge0()
290     {
291         return si() >= 0.0;
292     }
293 
294     /**
295      * Test if this Quantity is equal to 0.0.
296      * @return true if this is equal to 0.0; false if this is not equal to 0.0
297      */
298     public boolean eq0()
299     {
300         return si() == 0.0;
301     }
302 
303     /**
304      * Test if this Quantity is not equal to 0.0.
305      * @return true if this is not equal to 0.0; false if this is equal to 0.0
306      */
307     public boolean ne0()
308     {
309         return si() != 0.0;
310     }
311 
312     @Override
313     public int compareTo(final Q other)
314     {
315         return Double.compare(this.si(), other.si());
316     }
317 
318     @Override
319     public int hashCode()
320     {
321         return Objects.hash(this.displayUnit, this.si);
322     }
323 
324     @SuppressWarnings("checkstyle:needbraces")
325     @Override
326     public boolean equals(final Object obj)
327     {
328         if (this == obj)
329             return true;
330         if (obj == null)
331             return false;
332         if (getClass() != obj.getClass())
333             return false;
334         Quantity<?> other = (Quantity<?>) obj;
335         return Objects.equals(this.displayUnit, other.displayUnit)
336                 && Double.doubleToLongBits(this.si) == Double.doubleToLongBits(other.si);
337     }
338 
339     /**********************************************************************************/
340     /********************************** PARSING METHODS *******************************/
341     /**********************************************************************************/
342 
343     /**
344      * Returns a quantity for the textual representation of a value with a unit. The String representation that can be parsed is
345      * the double value in the unit, followed by a localized or English abbreviation of the unit. Spaces are allowed, but not
346      * required, between the value and the unit.
347      * @param text the textual representation to parse into the quantity
348      * @param example an example instance to deliver
349      * @return the quantity representation of the value with its unit
350      * @throws IllegalArgumentException when the text cannot be parsed
351      * @throws NullPointerException when the text argument is null
352      * @param <Q> the quantity type
353      */
354     @SuppressWarnings("unchecked")
355     public static <Q extends Quantity<Q>> Q valueOf(final String text, final Q example)
356     {
357         Throw.whenNull(example, "Error parsing Quantity: example is null");
358         String quantityClass = example.getClass().getSimpleName();
359         Throw.whenNull(text, "Error parsing Quantity: text to parse is null");
360         Throw.when(text.length() == 0, IllegalArgumentException.class, "Error parsing %s: empty text to parse", quantityClass);
361         try
362         {
363             NumberParser numberParser = new NumberParser().lenient().trailing();
364             double d = numberParser.parseDouble(text);
365 
366             // Everything after the parsed number is considered the unit token.
367             String unitStringRaw = text.substring(numberParser.getTrailingPosition());
368             String unitString = unitStringRaw.trim();
369 
370             Class<? extends UnitInterface<Q>> unitClass = (Class<UnitInterface<Q>>) example.getDisplayUnit().getClass();
371 
372             UnitInterface<Q> unit = null;
373             if (unitString.isEmpty())
374             {
375                 // Special-case: DIMENSIONLESS can omit the unit entirely ("" or all whitespace).
376                 if (Unitless.class.isAssignableFrom(unitClass))
377                 {
378                     unit = (UnitInterface<Q>) Unitless.BASE;
379                 }
380                 else
381                 {
382                     throw new IllegalArgumentException(
383                             String.format("Error parsing %s: missing unit in '%s'", quantityClass, text));
384                 }
385             }
386             else
387             {
388                 // Normal path: resolve the unit string for the quantity's unit class.
389                 UnitInterface<Q> resolved = (UnitInterface<Q>) Units.resolve(unitClass, unitString);
390                 Throw.when(resolved == null, IllegalArgumentException.class, "Unit '%s' not found for quantity %s", unitString,
391                         quantityClass);
392                 unit = resolved;
393             }
394 
395             return example.instantiate(d, unit);
396         }
397         catch (Exception exception)
398         {
399             throw new IllegalArgumentException("Error parsing " + quantityClass + " from " + text + " using Locale "
400                     + Locale.getDefault(Locale.Category.FORMAT), exception);
401         }
402     }
403 
404     /**
405      * Returns a quantity based on a value and the textual representation of the unit, which can be localized.
406      * @param valueInUnit the value, expressed in the unit as given by unitString
407      * @param unitString the textual representation of the unit
408      * @param example an example instance to deliver
409      * @return the quantity representation of the value in its unit
410      * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
411      * @throws NullPointerException when the unitString argument is null
412      * @param <Q> the quantity type
413      */
414     public static <Q extends Quantity<Q>> Q of(final double valueInUnit, final String unitString, final Q example)
415     {
416         Throw.whenNull(example, "Error parsing Quantity: example is null");
417         String quantityClass = example.getClass().getSimpleName();
418         Throw.whenNull(unitString, "Error parsing %s: unitString is null", quantityClass);
419         Throw.when(unitString.length() == 0, IllegalArgumentException.class, "Error parsing %s: empty unitString",
420                 quantityClass);
421         @SuppressWarnings("unchecked")
422         UnitInterface<Q> unit = (UnitInterface<Q>) Units.resolve(example.getDisplayUnit().getClass(), unitString);
423         Throw.when(unit == null, IllegalArgumentException.class, "Error parsing %s with unit %s", quantityClass, unitString);
424         return example.instantiate(valueInUnit, unit);
425     }
426 
427     /**********************************************************************************/
428     /*************************** STRING AND FORMATTING METHODS ************************/
429     /**********************************************************************************/
430 
431     /**
432      * Description of this quantity with default formatting.
433      * @return a String with the value of the quantity, with the unit attached.
434      */
435     @Override
436     public String toString()
437     {
438         return format();
439     }
440 
441     /**
442      * Concise description of this quantity.
443      * @return a String with the value of the quantity, with the unit attached.
444      */
445     @Override
446     public String format()
447     {
448         return format(QuantityFormat.instance());
449     }
450 
451     /**
452      * String representation of this quantity after applying the format.
453      * @param format the format to apply for the quantity
454      * @return a String representation of this quantity, formatted according to the given format
455      */
456     public String format(final QuantityFormat format)
457     {
458         return QuantityFormatter.format(this, format);
459     }
460 
461     /**
462      * String representation of this quantity, expressed in the specified unit.
463      * @param targetUnit the unit into which the quantity is converted for display
464      * @return printable string with the quantity value expressed in the specified unit
465      */
466     @Override
467     public String format(final UnitInterface<Q> targetUnit)
468     {
469         return format(QuantityFormat.instance().setDisplayUnit(targetUnit));
470     }
471 
472     /**********************************************************************************/
473     /********************* STATIC OPERATIONS ON MULTIPLE QUANTITIES *******************/
474     /**********************************************************************************/
475 
476     /**
477      * Interpolate between two quantities. Note that the first quantities does not have to be smaller than the second.
478      * @param zero the quantity at a ratio of zero
479      * @param one the quantity at a ratio of one
480      * @param ratio the ratio between 0 and 1, inclusive
481      * @return a Quantity at the given ratio between 0 and 1
482      * @param <Q> the quantity type
483      */
484     public static <Q extends Quantity<Q>> Q interpolate(final Q zero, final Q one, final double ratio)
485     {
486         Throw.when(ratio < 0.0 || ratio > 1.0, IllegalArgumentException.class,
487                 "ratio for interpolation should be between 0 and 1, but is %f", ratio);
488         return zero.instantiateSi(zero.si() * (1 - ratio) + one.si() * ratio, zero.getDisplayUnit());
489     }
490 
491     /**
492      * Return the maximum value of one or more quantities.
493      * @param quantity1 the first quantity
494      * @param quantities the other quantities
495      * @return the maximum value of the quantities
496      * @param <Q> the quantity type
497      */
498     @SafeVarargs
499     public static <Q extends Quantity<Q>> Q max(final Q quantity1, final Q... quantities)
500     {
501         Q maxQ = quantity1;
502         for (Q quantity : quantities)
503         {
504             if (quantity.gt(maxQ))
505             {
506                 maxQ = quantity;
507             }
508         }
509         return maxQ;
510     }
511 
512     /**
513      * Return the minimum value of one or more quantities.
514      * @param quantity1 the first quantity
515      * @param quantities the other quantities
516      * @return the minimum value of more than two quantities
517      * @param <Q> the quantity type
518      */
519     @SafeVarargs
520     public static <Q extends Quantity<Q>> Q min(final Q quantity1, final Q... quantities)
521     {
522         Q minQ = quantity1;
523         for (Q quantity : quantities)
524         {
525             if (quantity.lt(minQ))
526             {
527                 minQ = quantity;
528             }
529         }
530         return minQ;
531     }
532 
533     /**
534      * Return the sum of one or more quantities.
535      * @param quantity1 the first quantity
536      * @param quantities the other quantities
537      * @return the sum of the quantities
538      * @param <Q> the quantity type
539      */
540     @SafeVarargs
541     public static <Q extends Quantity<Q>> Q sum(final Q quantity1, final Q... quantities)
542     {
543         double sum = quantity1.si();
544         for (Q quantity : quantities)
545         {
546             sum += quantity.si();
547         }
548         return quantity1.instantiateSi(sum, quantity1.getDisplayUnit());
549     }
550 
551     /**
552      * Return the product of one or more quantities.
553      * @param quantity1 the first quantity
554      * @param quantities the other quantities
555      * @return the product of the quantities
556      */
557     @SafeVarargs
558     public static SIQuantity product(final Quantity<?> quantity1, final Quantity<?>... quantities)
559     {
560         double product = quantity1.si();
561         SIUnit unit = quantity1.siUnit();
562         for (var quantity : quantities)
563         {
564             product *= quantity.si();
565             unit = unit.plus(quantity.siUnit());
566         }
567         return new SIQuantity(product, unit);
568     }
569 
570     /**
571      * Return the mean of one or more quantities.
572      * @param quantity1 the first quantity
573      * @param quantities the other quantities
574      * @return the mean of the quantities
575      * @param <Q> the quantity type
576      */
577     @SafeVarargs
578     public static <Q extends Quantity<Q>> Q mean(final Q quantity1, final Q... quantities)
579     {
580         int n = 1 + quantities.length;
581         return sum(quantity1, quantities).divideBy(n);
582     }
583 
584     /***********************************************************************************/
585     /********************************* RELATIVE METHODS ********************************/
586     /***********************************************************************************/
587 
588     @Override
589     public Q add(final Q increment)
590     {
591         return instantiateSi(si() + increment.si(), getDisplayUnit());
592     }
593 
594     @Override
595     public Q subtract(final Q decrement)
596     {
597         return instantiateSi(si() - decrement.si(), getDisplayUnit());
598     }
599 
600     @Override
601     public Q abs()
602     {
603         return instantiateSi(Math.abs(si()), getDisplayUnit());
604     }
605 
606     @Override
607     public Q negate()
608     {
609         return instantiateSi(-si(), getDisplayUnit());
610     }
611 
612     @Override
613     public Q scaleBy(final double factor)
614     {
615         return instantiateSi(si() * factor, getDisplayUnit());
616     }
617 
618     /**
619      * Multiply this quantity with another quantity, and return a SIQuantity as the result.
620      * @param quantity the quantity to multiply with
621      * @return the multiplication of this quantity and the given quantity
622      */
623     public SIQuantity multiply(final Quantity<?> quantity)
624     {
625         SIUnit siUnit = SIUnit.add(siUnit(), quantity.siUnit());
626         return new SIQuantity(si() * quantity.si(), siUnit);
627     }
628 
629     /**
630      * Divide this quantity by another quantity, and return a SIQuantity as the result.
631      * @param quantity the quantity to divide by
632      * @return the division of this quantity and the given quantity
633      */
634     public SIQuantity divide(final Quantity<?> quantity)
635     {
636         SIUnit siUnit = SIUnit.subtract(siUnit(), quantity.siUnit());
637         return new SIQuantity(si() / quantity.si(), siUnit);
638     }
639 
640     /**
641      * Return the reciprocal of this quantity (1/q).
642      * @return the reciprocal of this quantity, with the correct SI units
643      */
644     public Quantity<?> reciprocal()
645     {
646         return new SIQuantity(1.0 / si(), this.siUnit().invert());
647     }
648 
649     /**
650      * Return the quantity 'as' a known quantity, using a unit to express the result in. Throw a Runtime exception when the SI
651      * units of this quantity and the target quantity do not match.
652      * @param targetUnit the unit to convert the quantity to
653      * @return a quantity typed in the target quantity class
654      * @throws IllegalArgumentException when the units do not match
655      * @param <TQ> target quantity type
656      */
657     public <TQ extends Quantity<TQ>> TQ as(final UnitInterface<TQ> targetUnit) throws IllegalArgumentException
658     {
659         Throw.when(!siUnit().equals(targetUnit.siUnit()), IllegalArgumentException.class,
660                 "Quantity.as(%s) called, but units do not match: %s <> %s", targetUnit, siUnit().getDisplayAbbreviation(),
661                 targetUnit.siUnit().getDisplayAbbreviation());
662         return targetUnit.ofSi(si(), targetUnit);
663     }
664 
665     @Override
666     public boolean isRelative()
667     {
668         return true;
669     }
670 
671 }