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