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.Direction;
9   import org.djunits.quantity.Position;
10  import org.djunits.quantity.Temperature;
11  import org.djunits.quantity.Time;
12  import org.djunits.unit.UnitInterface;
13  import org.djunits.unit.Units;
14  import org.djunits.unit.si.SIUnit;
15  import org.djunits.util.SuppressFBWarnings;
16  import org.djunits.value.Value;
17  import org.djutils.base.NumberParser;
18  import org.djutils.exceptions.Throw;
19  
20  /**
21   * AbsQuantity stores the basic information about a absolute quantity and implements the basic operations that hold for all
22   * absolute quantities. An absolute quantity wraps a relative Quantity and has a reference point that acts as an origin or zero
23   * point. Note that the absolute quantity {@link Direction} directly extends {@link AbsQuantity} because of its circular scale.
24   * The other absolute quantities {@link Position}, {@link Temperature} and {@link Time} extends {@link ComparableAbsQuantity}
25   * that extends this class and implements comparators as well.
26   * <p>
27   * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
28   * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
29   * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
30   * @author Alexander Verbraeck
31   * @param <A> the absolute quantity type
32   * @param <Q> the relative quantity type
33   * @param <R> the reference type to use for the absolute quantity
34   */
35  public abstract class AbsQuantity<A extends AbsQuantity<A, Q, R>, Q extends Quantity<Q>, R extends Reference<R, A, Q>>
36          implements Value<Q>
37  {
38      /** */
39      private static final long serialVersionUID = 600L;
40  
41      /** The relative quantity. */
42      private final Q quantity;
43  
44      /** The reference point. */
45      private final R reference;
46  
47      /**
48       * Instantiate an absolute quantity with a quantity and a reference.
49       * @param quantity the relative quantity that indicates the 'distance' to the reference point
50       * @param reference the reference point
51       */
52      @SuppressFBWarnings("EI_EXPOSE_REP2") // quantity is immutable
53      public AbsQuantity(final Q quantity, final R reference)
54      {
55          Throw.whenNull(quantity, "quantity");
56          Throw.whenNull(reference, "reference");
57          this.quantity = quantity;
58          this.reference = reference;
59      }
60  
61      /**********************************************************************************/
62      /******************************* UNIT-RELATED METHODS *****************************/
63      /**********************************************************************************/
64  
65      @Override
66      public UnitInterface<Q> getDisplayUnit()
67      {
68          return this.quantity.getDisplayUnit();
69      }
70  
71      /**
72       * Retrieve the relative quantity value in the current display unit.
73       * @return the relative quantity value in the current display unit
74       */
75      public double getInUnit()
76      {
77          return getDisplayUnit().getScale().fromIdentityScale(si());
78      }
79  
80      /**
81       * Retrieve the relative quantity value converted into some specified unit.
82       * @param targetUnit the unit to convert the relative quantity value into
83       * @return the value of the relative quantity 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 (relative) quantity relative to the reference.
92       * @return the (relative) quantity relative to the reference
93       */
94      @SuppressFBWarnings("EI_EXPOSE_REP") // quantity is immutable
95      public Q getQuantity()
96      {
97          return this.quantity;
98      }
99  
100     /**
101      * Return the reference point (zero or origin).
102      * @return the reference point
103      */
104     public R getReference()
105     {
106         return this.reference;
107     }
108 
109     /**
110      * Return the "pretty" and localized name of the quantity.
111      * @return the "pretty" and localized name of the quantity
112      */
113     public String getName()
114     {
115         String name = Units.localizedQuantityName(Locale.getDefault(), getClass().getSimpleName());
116         final StringBuilder sb = new StringBuilder(name.length() + 8);
117         sb.append(name.charAt(0)); // keep first character exactly as-is
118         for (int i = 1; i < name.length(); i++)
119         {
120             final char c = name.charAt(i);
121             if (Character.isUpperCase(c))
122             {
123                 if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ')
124                 {
125                     sb.append(' ');
126                 }
127                 sb.append(Character.toLowerCase(c));
128             }
129             else
130             {
131                 sb.append(c);
132             }
133         }
134         return sb.toString();
135     }
136 
137     /**********************************************************************************/
138     /******************************** SI-RELATED METHODS ******************************/
139     /**********************************************************************************/
140 
141     /**
142      * Return the SI unit of this quantity.
143      * @return the SI unit of this quantity
144      */
145     public SIUnit siUnit()
146     {
147         return getDisplayUnit().siUnit();
148     }
149 
150     /**
151      * Return the SI value of the quantity.
152      * @return the SI value of the quantity
153      */
154     public double si()
155     {
156         return this.quantity.si();
157     }
158 
159     /**
160      * Instantiate an absolute quantity with a quantity and a reference.
161      * @param quantity the relative quantity that indicates the 'distance' to the reference point
162      * @param reference the reference point
163      * @return the absolute quantity with a quantity and a reference
164      */
165     @SuppressWarnings("checkstyle:hiddenfield")
166     public abstract A instantiate(Q quantity, R reference);
167 
168     /**********************************************************************************/
169     /******************************** HASHCODE AND EQUALS *****************************/
170     /**********************************************************************************/
171 
172     @Override
173     public int hashCode()
174     {
175         return Objects.hash(this.quantity, this.reference);
176     }
177 
178     @SuppressWarnings("checkstyle:needbraces")
179     @Override
180     public boolean equals(final Object obj)
181     {
182         if (this == obj)
183             return true;
184         if (obj == null)
185             return false;
186         if (getClass() != obj.getClass())
187             return false;
188         AbsQuantity<?, ?, ?> other = (AbsQuantity<?, ?, ?>) obj;
189         return Objects.equals(this.quantity, other.quantity) && Objects.equals(this.reference, other.reference);
190     }
191 
192     /**********************************************************************************/
193     /********************************** PARSING METHODS *******************************/
194     /**********************************************************************************/
195 
196     /**
197      * Returns an absolute quantity for the textual representation of a value with a unit. The String representation that can be
198      * parsed is the double value in the unit, followed by a localized or English abbreviation of the unit. Spaces are allowed,
199      * but not required, between the value and the unit.
200      * @param text the textual representation to parse into the quantity
201      * @param example an example instance to deliver
202      * @param reference the reference point
203      * @return the absolute quantity representation of the value with its unit
204      * @throws IllegalArgumentException when the text cannot be parsed
205      * @throws NullPointerException when the text argument is null
206      * @param <A> the absolute quantity type
207      * @param <Q> the relative quantity type
208      * @param <R> the reference type to use for the absolute quantity
209      */
210     public static <A extends AbsQuantity<A, Q, R>, Q extends Quantity<Q>,
211             R extends Reference<R, A, Q>> A valueOf(final String text, final A example, final R reference)
212     {
213         Throw.whenNull(example, "Error parsing AbsQuantity: example is null");
214         String quantityClass = example.getClass().getSimpleName();
215         Throw.whenNull(text, "Error parsing AbsQuantity: text to parse is null");
216         Throw.when(text.length() == 0, IllegalArgumentException.class, "Error parsing %s: empty text to parse", quantityClass);
217         Throw.whenNull(reference, "Error parsing AbsQuantity: reference is null");
218         try
219         {
220             NumberParser numberParser = new NumberParser().lenient().trailing();
221             double d = numberParser.parseDouble(text);
222             String unitString = text.substring(numberParser.getTrailingPosition()).trim();
223             @SuppressWarnings("unchecked")
224             UnitInterface<Q> unit = (UnitInterface<Q>) Units.resolve(example.getDisplayUnit().getClass(), unitString);
225             Throw.when(unit == null, IllegalArgumentException.class, "Unit %s not found for quantity %s", unitString,
226                     quantityClass);
227             return example.instantiate(example.getQuantity().instantiate(d, unit), reference);
228         }
229         catch (Exception exception)
230         {
231             throw new IllegalArgumentException("Error parsing " + quantityClass + " from " + text + " using Locale "
232                     + Locale.getDefault(Locale.Category.FORMAT), exception);
233         }
234     }
235 
236     /**
237      * Returns an absolute quantity based on a value and the textual representation of the unit, which can be localized.
238      * @param valueInUnit the value, expressed in the unit as given by unitString
239      * @param unitString the textual representation of the unit
240      * @param example an absolute example instance to deliver
241      * @param reference the reference point
242      * @return the absolute quantity representation of the value in its unit
243      * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
244      * @throws NullPointerException when the unitString argument is null
245      * @param <A> the absolute quantity type
246      * @param <Q> the relative quantity type
247      * @param <R> the reference type to use for the absolute quantity
248      */
249     public static <A extends AbsQuantity<A, Q, R>, Q extends Quantity<Q>, R extends Reference<R, A, Q>> A of(
250             final double valueInUnit, final String unitString, final A example, final R reference)
251     {
252         Throw.whenNull(example, "Error parsing AbsQuantity: example is null");
253         String quantityClass = example.getClass().getSimpleName();
254         Throw.whenNull(unitString, "Error parsing %s: unitString is null", quantityClass);
255         Throw.when(unitString.length() == 0, IllegalArgumentException.class, "Error parsing %s: empty unitString",
256                 quantityClass);
257         Throw.whenNull(reference, "Error parsing AbsQuantity: reference is null");
258         @SuppressWarnings("unchecked")
259         UnitInterface<Q> unit = (UnitInterface<Q>) Units.resolve(example.getDisplayUnit().getClass(), unitString);
260         Throw.when(unit == null, IllegalArgumentException.class, "Error parsing %s with unit %s", quantityClass, unitString);
261         return example.instantiate(example.getQuantity().instantiate(valueInUnit, unit), reference);
262     }
263 
264     /**********************************************************************************/
265     /*************************** STRING AND FORMATTING METHODS ************************/
266     /**********************************************************************************/
267 
268     /**
269      * Return the quantity relative to another reference point.
270      * @param otherReference the reference point to which it has to be defined relatively.
271      * @return the absolute quantity relative to the other reference point
272      * @throws IllegalArgumentException when there is no translation from the current reference point to the provided reference
273      */
274     @SuppressWarnings({"unchecked", "checkstyle:needbraces"})
275     public A relativeTo(final R otherReference)
276     {
277         if (getReference().equals(otherReference))
278             return (A) this;
279         if (getReference().equals(otherReference.getOffsetReference()))
280             return instantiate(getQuantity().subtract(otherReference.getOffset()), otherReference);
281         var offsetReference = getReference().getOffsetReference();
282         Throw.when(offsetReference == null, IllegalArgumentException.class,
283                 "Reference %s cannot be transformed to a base reference for a transformation", getReference().getId());
284         if (offsetReference.equals(otherReference))
285             return instantiate(getQuantity().add(getReference().getOffset()), otherReference);
286         if (offsetReference.equals(otherReference.getOffsetReference()))
287             return instantiate(getQuantity().add(getReference().getOffset()).subtract(otherReference.getOffset()),
288                     otherReference);
289         throw new IllegalArgumentException(String.format("Reference %s cannot be transformed to reference %s",
290                 getReference().getId(), otherReference.getId()));
291     }
292 
293     /**********************************************************************************/
294     /*************************** STRING AND FORMATTING METHODS ************************/
295     /**********************************************************************************/
296 
297     /**
298      * Description of this quantity with default formatting.
299      * @return a String with the value of the quantity, with the unit attached.
300      */
301     @Override
302     public String toString()
303     {
304         return format();
305     }
306 
307     /**
308      * Concise description of this quantity.
309      * @return a String with the value of the quantity, with the unit attached.
310      */
311     @Override
312     public String format()
313     {
314         return format(QuantityFormat.instance());
315     }
316 
317     /**
318      * String representation of this quantity after applying the format.
319      * @param format the format to apply for the quantity
320      * @return a String representation of this quantity, formatted according to the given format
321      */
322     public String format(final QuantityFormat format)
323     {
324         return QuantityFormatter.format(this, format);
325     }
326 
327     /**
328      * String representation of this quantity, expressed in the specified unit.
329      * @param targetUnit the unit into which the quantity is converted for display
330      * @return printable string with the quantity value expressed in the specified unit
331      */
332     @Override
333     public String format(final UnitInterface<Q> targetUnit)
334     {
335         return format(QuantityFormat.instance().setDisplayUnit(targetUnit));
336     }
337 
338     /**********************************************************************************/
339     /************************************ OPERATIONS **********************************/
340     /**********************************************************************************/
341 
342     /**
343      * Subtract two absolute quantities from each other, resulting in the corresponding relative quantity. The unit of the
344      * resulting quantity will be the unit of 'this' absolute quantity. Quantity 'other' will be transformed to the reference
345      * point of this absolute quantity. If the reference points of this and other are different, and no transformations between
346      * the reference points exist, an exception will be thrown.
347      * @param other the absolute quantity to subtract
348      * @return the relative quantity as a result of the subtraction
349      * @throws IllegalArgumentException when the reference points are unequal and cannot be transformed to each other
350      */
351     public abstract Q subtract(A other);
352 
353     /**
354      * Add a relative quantity to this absolute quantity, resulting in a new absolute quantity containing the sum. The new
355      * quantity will have the same reference point and unit as this absolute quantity.
356      * @param other the relative quantity to add
357      * @return the absolute quantity as a result of the addition
358      */
359     public abstract A add(Q other);
360 
361     /**
362      * Subtract a relative quantity from this absolute quantity, resulting in a new absolute quantity containing the difference.
363      * The new quantity will have the same reference point and unit as this absolute quantity.
364      * @param other the relative quantity to subtract
365      * @return the absolute quantity as a result of the subtraction
366      */
367     public abstract A subtract(Q other);
368 
369     @Override
370     public boolean isRelative()
371     {
372         return false;
373     }
374 
375 }