001/* 002 * ============================================================================ 003 * Copyright © 2002-2026 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.lang.value; 019 020import org.apiguardian.api.API; 021import org.tquadrat.foundation.annotation.ClassVersion; 022import org.tquadrat.foundation.exception.UnexpectedExceptionError; 023 024import java.io.Serializable; 025import java.lang.reflect.Constructor; 026import java.lang.reflect.InvocationTargetException; 027import java.math.BigDecimal; 028import java.math.MathContext; 029import java.util.Formattable; 030import java.util.Formatter; 031import java.util.IllegalFormatException; 032import java.util.Locale; 033 034import static java.lang.String.format; 035import static java.math.RoundingMode.HALF_EVEN; 036import static java.util.FormattableFlags.LEFT_JUSTIFY; 037import static org.apiguardian.api.API.Status.STABLE; 038import static org.tquadrat.foundation.lang.Objects.isNull; 039import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 040import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 041 042/** 043 * <p>{@summary The definition for a value with a dimension.}</p> 044 * <p>Although the unit for the dimension may be changed, instances of 045 * classes implementing this interface can be assumed to be immutable as the 046 * <i>value</i> remains always the same. So at least the results of the 047 * methods 048 * {@link #equals(Object)}, 049 * {@link #hashCode()}, 050 * and 051 * {@link #compareTo(DimensionedValue)} 052 * will remain always the same 053 * (while the results from 054 * {@link #toString()} 055 * may differ after a call to 056 * {@link #setUnit(Dimension)}).</p> 057 * <p>All concrete (non-abstract) implementations of this interface should be 058 * {@code final}.</p> 059 * 060 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 061 * @version $Id: DimensionedValue.java 1195 2026-04-15 21:33:40Z tquadrat $ 062 * @since 0.15.1 063 * 064 * @param <D> The dimension. 065 * 066 * @UMLGraph.link 067 */ 068@SuppressWarnings( "ClassWithTooManyMethods" ) 069@ClassVersion( sourceVersion = "$Id: DimensionedValue.java 1195 2026-04-15 21:33:40Z tquadrat $" ) 070@API( status = STABLE, since = "0.25.1" ) 071public sealed interface DimensionedValue<D extends Dimension> extends Cloneable, Comparable<DimensionedValue<D>>, Formattable, Serializable 072 permits ValueBase 073{ 074 /*-----------*\ 075 ====** Constants **======================================================== 076 \*-----------*/ 077 /** 078 * The 079 * {@link MathContext} 080 * that is used for all the operations with dimensioned values. 081 */ 082 public static final MathContext MATH_CONTEXT = new MathContext( 128, HALF_EVEN ); 083 084 /*---------*\ 085 ====** Methods **========================================================== 086 \*---------*/ 087 /** 088 * Returns the base unit of the dimension for the value. 089 * 090 * @return The base unit. 091 */ 092 public default D baseUnit() 093 { 094 @SuppressWarnings( "unchecked" ) 095 final var retValue = (D) getUnit().baseUnit(); 096 097 //---* Done *---------------------------------------------------------- 098 return retValue; 099 } // baseUnit() 100 101 /** 102 * <p>{@summary Returns the base value (this value, converted to the base 103 * unit).}</p> 104 * <p>According to the result, this is the same as calling</p> 105 * <div class="source-container"><pre>convert( baseUnit() );</pre></div> 106 * 107 * @return The numerical value as for the base unit. 108 * 109 * @see #convert(Dimension) 110 */ 111 public BigDecimal baseValue(); 112 113 /** 114 * Creates a new copy of this value. 115 * 116 * @return The copy. 117 */ 118 public DimensionedValue<D> clone(); 119 120 /** 121 * {@inheritDoc} 122 * <p>The comparison is made based on the 123 * {@link #baseValue()}.</p> 124 */ 125 @Override 126 public default int compareTo( final DimensionedValue<D> other ) 127 { 128 final var retValue = Integer.signum( baseValue().compareTo( requireNonNullArgument( other, "other" ).baseValue() ) ); 129 130 //---* Done *---------------------------------------------------------- 131 return retValue; 132 } // compareTo() 133 134 /** 135 * Converts this value to the given unit and returns the numerical value. 136 * 137 * @param unit The unit. 138 * @return The numerical value of this instance, based on the provided 139 * unit. 140 * 141 * @see #baseValue() 142 */ 143 public default BigDecimal convert( final D unit ) 144 { 145 final var conversion = requireNonNullArgument( unit, "unit" ).fromBase(); 146 final var retValue = conversion.apply( baseValue() ).stripTrailingZeros(); 147 148 //---* Done *---------------------------------------------------------- 149 return retValue; 150 } // convert() 151 152 /** 153 * Creates a new copy of this value. 154 * 155 * @return The copy. 156 * 157 * @see Object#clone() 158 */ 159 public DimensionedValue<D> copy(); 160 161 /** 162 * Creates a new copy of this value. 163 * 164 * @param unit The unit for the new copy. 165 * @return The copy. 166 * 167 * @see Object#clone() 168 */ 169 public DimensionedValue<D> copy( final D unit ); 170 171 /** 172 * Divides the value by a dimension-less value and returns the result 173 * without changing this instance. 174 * 175 * @param divisor The divisor. 176 * @return The new value. 177 */ 178 public default DimensionedValue<D> divide( final BigDecimal divisor ) 179 { 180 final var retValue = newInstance( baseUnit(), baseValue().divide( requireNonNullArgument( divisor, "divisor" ), MATH_CONTEXT ) ); 181 retValue.setUnit( getUnit() ); 182 183 //---* Done *---------------------------------------------------------- 184 return retValue; 185 } // divide() 186 187 /** 188 * Divides the value by a dimension-less value and returns the result 189 * without changing this instance. 190 * 191 * @param divisor The divisor. 192 * @return The new value. 193 */ 194 public default DimensionedValue<D> divide( final Number divisor ) 195 { 196 final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ).toString() ) ); 197 retValue.setUnit( getUnit() ); 198 199 //---* Done *---------------------------------------------------------- 200 return retValue; 201 } // divide() 202 203 /** 204 * Divides the value by a dimension-less value and returns the result 205 * without changing this instance. 206 * 207 * @param divisor The divisor; it must be possible to parse the given 208 * String into a 209 * {@link BigDecimal}. 210 * @return The new value. 211 * @throws NumberFormatException The provided value cannot be converted 212 * into a {@code BigDecimal}. 213 * 214 * @see BigDecimal#BigDecimal(String) 215 */ 216 public default DimensionedValue<D> divide( final String divisor ) 217 { 218 final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ) ) ); 219 220 //---* Done *---------------------------------------------------------- 221 return retValue; 222 } // divide() 223 224 /** 225 * {@inheritDoc} 226 * <p>Two instances of a class implementing this interface are equals if 227 * they are of the <i>same</i> class and if their values, converted to the 228 * base dimension, are equals.</p> 229 * 230 * @param o The other value. 231 * @return {@code true} if they are equal, {@code false} if not. 232 * 233 * @see Dimension#baseUnit() 234 */ 235 @Override 236 public boolean equals( final Object o ); 237 238 /** 239 * {@inheritDoc} 240 * <p>The precision is applied to the numerical part only. The width 241 * includes the 242 * {@linkplain Dimension#unitSymbol() unit symbol}, 243 * too.</p> 244 * 245 * @note In case the {@code formatter} argument is {@code null}, this 246 * method throws a {@code NullPointerException} and <i>not</i> the 247 * usual {@code NullArgumentException}, because this method is usually 248 * called by instances of {@code java.util.Formatter}, and those do 249 * not know about our special exceptions. 250 * 251 * @throws NullPointerException The {@code formatter} argument is 252 * {@code null}. 253 * 254 * @see Formatter 255 */ 256 @SuppressWarnings( "ProhibitedExceptionThrown" ) 257 @Override 258 public default void formatTo( final Formatter formatter, final int flags, final int width, final int precision ) 259 { 260 if( isNull( formatter ) ) throw new NullPointerException( "formatter is null" ); 261 262 var string = toString( width, precision ).trim(); 263 final var len = string.length(); 264 265 if( ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) && (width > len) ) 266 { 267 string = string.concat( " ".repeat( width - len ) ); 268 } 269 270 /* 271 * We do not use Formatter.out().append() because we do not know how to 272 * handle the IOException that could be thrown from 273 * Appendable.append(). Using Formatter.format() assumes that Formatter 274 * knows ... 275 */ 276 formatter.format( "%s", string ); 277 } // formatTo() 278 279 /** 280 * Returns the unit for the value. 281 * 282 * @return The unit. 283 */ 284 public D getUnit(); 285 286 /** 287 * {@inheritDoc} 288 * <p>The hash code is based on the 289 * {@linkplain #baseValue() base value} 290 * and 291 * {@linkplain #baseUnit() base unit} 292 * only.</p> 293 */ 294 @Override 295 public int hashCode(); 296 297 /** 298 * Multiplies the value by a dimension-less value and returns the result 299 * without changing this instance. 300 * 301 * @param multiplicand The multiplier. 302 * @return The new value. 303 */ 304 public default DimensionedValue<D> multiply( final BigDecimal multiplicand ) 305 { 306 final var retValue = newInstance( baseUnit(), baseValue().multiply( requireNonNullArgument( multiplicand, "multiplicand" ) ) ); 307 retValue.setUnit( getUnit() ); 308 309 //---* Done *---------------------------------------------------------- 310 return retValue; 311 } // multiply() 312 313 /** 314 * Multiplies the value by a dimension-less value and returns the result 315 * without changing this instance. 316 * 317 * @param multiplicand The multiplier. 318 * @return The new value. 319 */ 320 public default DimensionedValue<D> multiply( final Number multiplicand ) 321 { 322 final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ).toString() ) ); 323 retValue.setUnit( getUnit() ); 324 325 //---* Done *---------------------------------------------------------- 326 return retValue; 327 } // multiply() 328 329 /** 330 * Multiplies the value by a dimension-less value and returns the result 331 * without changing this instance. 332 * 333 * @param multiplicand The multiplier; it must be possible to parse the 334 * given String into a 335 * {@link BigDecimal}. 336 * @return The new value. 337 * @throws NumberFormatException The provided value cannot be converted 338 * into a {@code BigDecimal}. 339 * 340 * @see BigDecimal#BigDecimal(String) 341 */ 342 public default DimensionedValue<D> multiply( final String multiplicand ) 343 { 344 final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ) ) ); 345 346 //---* Done *---------------------------------------------------------- 347 return retValue; 348 } // multiply() 349 350 /** 351 * Creates an instance for the class. 352 * 353 * @param dimension The dimension for the new instance. 354 * @param value The value for the new instance. 355 * @return The new instance. 356 */ 357 public default DimensionedValue<D> newInstance( final D dimension, final BigDecimal value ) 358 { 359 @SuppressWarnings( "UnusedAssignment" ) 360 DimensionedValue<D> retValue = null; 361 362 try 363 { 364 //---* Let's get the constructor *--------------------------------- 365 final Constructor<DimensionedValue<D>> constructor; 366 if( requireNonNullArgument( dimension, "dimension" ) instanceof final Enum<?> enumConstant ) 367 { 368 final var enumClass = enumConstant.getDeclaringClass(); 369 //noinspection unchecked 370 constructor = (Constructor<DimensionedValue<D>>) getClass().getConstructor( enumClass, BigDecimal.class ); 371 } 372 else 373 { 374 throw new IllegalArgumentException( "Invalid type for 'dimension': %s".formatted( dimension.getClass().getName() ) ); 375 } 376 377 //---* Create the new value *---------------------------------- 378 retValue = constructor.newInstance( dimension, requireNonNullArgument( value, "value" ) ); 379 } 380 catch( final NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException e ) 381 { 382 throw new UnexpectedExceptionError( e ); 383 } 384 385 //---* Done *---------------------------------------------------------- 386 return retValue; 387 } // newInstance() 388 389 /** 390 * Applies another unit for the value. This does not affect the results 391 * of 392 * {@link #equals(Object)}, 393 * {@link #hashCode()}} 394 * and 395 * {@link #compareTo(DimensionedValue)}, 396 * nor that of 397 * {@link #baseValue()}. 398 * 399 * @param unit The new unit. 400 */ 401 public void setUnit( D unit ); 402 403 /** 404 * Creates a new instance with the sum of this and the given value, and 405 * returns that. 406 * 407 * @param unit The unit for the new instance. 408 * @param summand The value to add. 409 * @return The instance with the sum. 410 */ 411 public default DimensionedValue<D> sum( final D unit, final DimensionedValue<D> summand ) 412 { 413 final var retValue = sum( summand ); 414 retValue.setUnit( unit ); 415 416 //---* Done *---------------------------------------------------------- 417 return retValue; 418 } // sum() 419 420 /** 421 * Creates a new instance with the sum of this and the given value, and 422 * returns that. The unit for the new instance is that of this instance. 423 * 424 * @param summand The value to add. 425 * @return The instance with the sum. 426 */ 427 public default DimensionedValue<D> sum( final DimensionedValue<D> summand ) 428 { 429 final var newValue = baseValue().add( requireNonNullArgument( summand, "summand" ).baseValue(), MATH_CONTEXT ); 430 final var retValue = newInstance( baseUnit(), newValue ); 431 432 //---* Done *---------------------------------------------------------- 433 return retValue; 434 } // sum() 435 436 /** 437 * <p>{@summary Returns the String representation for this value}; 438 * usually, this is in the format</p> 439 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 440 * <p>like "{@code 4.5 m}".</p> 441 * <p>The precision for the mantissa is 442 * provided by the 443 * {@linkplain Dimension#getPrecision() unit}.</p> 444 * <p>If more control over the output format is required, see 445 * {@link #toString(int, int)}.</p> 446 */ 447 @Override 448 public String toString(); 449 450 /** 451 * <p>{@summary Provides a String representation of this value}, in the 452 * format</p> 453 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 454 * <p>for the given 455 * {@link Locale} 456 * that determines the decimal separator, like "{@code 4.5 m}" 457 * vs. "{@code 4,5 m}".</p> 458 * <p>The precision is applied to the numerical part only. The width 459 * includes the 460 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 461 * 462 * @param locale The locale to use. 463 * @param width The minimum number of characters to be written to the 464 * output. If the length of the converted value is less than the width 465 * then the output will be padded by ' ' until the total number 466 * of characters equals width. The padding is at the beginning, as 467 * numerical values are usually right justified. If {@code width} is 468 * -1 then there is no minimum. 469 * @param precision – The number of digits for the mantissa of the value. 470 * If {@code precision} is -1 then there is no explicit limit on the 471 * size of the mantissa. 472 * @return The String representation for this value. 473 */ 474 public default String toString( final Locale locale, final int width, final int precision ) 475 { 476 final var retValue = toString( locale, width, precision, false ); 477 478 //---* Done *---------------------------------------------------------- 479 return retValue; 480 } // toString() 481 482 /** 483 * <p>{@summary Provides a String representation of this value}, in the 484 * format</p> 485 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 486 * <p>for the given 487 * {@link Locale} 488 * that determines the decimal separator, like "{@code 4.5 m}" 489 * vs. "{@code 4,5 m}".</p> 490 * <p>The precision is applied to the numerical part only. The width 491 * includes the 492 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 493 * 494 * @param locale The locale to use. 495 * @param width The minimum number of characters to be written to the 496 * output. If the length of the converted value is less than the width 497 * then the output will be padded by ' ' until the total number 498 * of characters equals width. The padding is at the beginning, as 499 * numerical values are usually right justified. If {@code width} is 500 * -1 then there is no minimum. 501 * @param precision – The number of digits for the mantissa of the value. 502 * If {@code precision} is -1 then there is no explicit limit on the 503 * size of the mantissa. 504 * @param useNiceUnit {@code true} if the method 505 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 506 * should be used to retrieve the unit symbol, {@code false} if the 507 * usual one is sufficient. 508 * @return The String representation for this value. 509 */ 510 public default String toString( final Locale locale, final int width, final int precision, final boolean useNiceUnit ) 511 { 512 final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol(); 513 final var effectiveWidth = width - unitSymbol.length() - 1; 514 515 final var format = new StringBuilder( "%" ); 516 if( effectiveWidth > 0 ) format.append( effectiveWidth ); 517 if( precision >= 0 ) format.append( "." ).append( precision ); 518 format.append( "f %s" ); 519 520 final var retValue = format( requireNonNullArgument( locale, "locale" ), format.toString(), value(), unitSymbol ); 521 522 //---* Done *---------------------------------------------------------- 523 return retValue; 524 } // toString() 525 526 /** 527 * <p>{@summary Provides a String representation of this value}, in the 528 * format</p> 529 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 530 * <p>and for the 531 * {@linkplain Locale#getDefault() default Locale}, 532 * like "{@code 4.5 m}", where the Locale determines the decimal 533 * separator.</p> 534 * <p>The precision is applied to the numerical part only. The width 535 * includes the 536 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 537 * 538 * @param width The minimum number of characters to be written to the 539 * output. If the length of the converted value is less than the width 540 * then the output will be padded by ' ' until the total number 541 * of characters equals width. The padding is at the beginning, as 542 * numerical values are usually right justified. If {@code width} is 543 * -1 then there is no minimum. 544 * @param precision – The number of digits for the mantissa of the value. 545 * If {@code precision} is -1 then there is no explicit limit on the 546 * size of the mantissa. 547 * @return The String representation for this value. 548 */ 549 public default String toString( final int width, final int precision ) 550 { 551 final var retValue = toString( Locale.getDefault(), width, precision ); 552 553 //---* Done *---------------------------------------------------------- 554 return retValue; 555 } // toString() 556 557 /** 558 * <p>{@summary Provides a String representation of this value}, in the 559 * format that is defined by the provided format String.</p> 560 * <p>That format String must contain exactly one '%f' tag and one '%s' 561 * tag; the first takes the numerical value, the second the unit.</p> 562 * <p>The provided 563 * {@link Locale} 564 * determines the decimal separator and the optional thousands' 565 * separator.</p> 566 * 567 * @param locale The locale to use. 568 * @param format The format String. 569 * @param useNiceUnit {@code true} if the method 570 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 571 * should be used to retrieve the unit symbol, {@code false} if the 572 * usual one is sufficient. 573 * @return The String representation for this value. 574 * @throws IllegalFormatException The provided format String is invalid. 575 * 576 * @see Formatter 577 */ 578 public default String toString( final Locale locale, final String format, final boolean useNiceUnit ) throws IllegalFormatException 579 { 580 final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol(); 581 final var retValue = format( requireNonNullArgument( locale, "locale" ), requireNotEmptyArgument( format, "format" ), value(), unitSymbol ); 582 583 //---* Done *---------------------------------------------------------- 584 return retValue; 585 } // toString() 586 587 /** 588 * Returns the numerical value. 589 * 590 * @return The numerical value, based on current dimension. 591 */ 592 public default BigDecimal value() { return convert( getUnit() ); } 593} 594// interface DimensionedValue 595 596/* 597 * End of File 598 */