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 }