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