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 }