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 static java.lang.String.format; 021import static java.math.RoundingMode.HALF_EVEN; 022import static java.util.FormattableFlags.LEFT_JUSTIFY; 023import static org.apiguardian.api.API.Status.STABLE; 024import static org.tquadrat.foundation.lang.Objects.isNull; 025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 026import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 027 028import java.io.Serializable; 029import java.lang.reflect.Constructor; 030import java.lang.reflect.InvocationTargetException; 031import java.math.BigDecimal; 032import java.math.MathContext; 033import java.util.Formattable; 034import java.util.Formatter; 035import java.util.IllegalFormatException; 036import java.util.Locale; 037 038import org.apiguardian.api.API; 039import org.tquadrat.foundation.annotation.ClassVersion; 040import org.tquadrat.foundation.exception.UnexpectedExceptionError; 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 1286 2026-09-09 09:43:11Z 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 1286 2026-09-09 09:43:11Z 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 {@true} if they are equal, {@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 {@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 * {@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 DimensionedValue<D> retValue = null; 360 361 try 362 { 363 //---* Let's get the constructor *--------------------------------- 364 final Constructor<DimensionedValue<D>> constructor; 365 if( requireNonNullArgument( dimension, "dimension" ) instanceof final Enum<?> enumConstant ) 366 { 367 final var enumClass = enumConstant.getDeclaringClass(); 368 //noinspection unchecked 369 constructor = (Constructor<DimensionedValue<D>>) getClass().getConstructor( enumClass, BigDecimal.class ); 370 } 371 else 372 { 373 throw new IllegalArgumentException( "Invalid type for 'dimension': %s".formatted( dimension.getClass().getName() ) ); 374 } 375 376 //---* Create the new value *---------------------------------- 377 retValue = constructor.newInstance( dimension, requireNonNullArgument( value, "value" ) ); 378 } 379 catch( final NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException e ) 380 { 381 throw new UnexpectedExceptionError( e ); 382 } 383 384 //---* Done *---------------------------------------------------------- 385 return retValue; 386 } // newInstance() 387 388 /** 389 * Applies another unit for the value. This does not affect the results 390 * of 391 * {@link #equals(Object)}, 392 * {@link #hashCode()}} 393 * and 394 * {@link #compareTo(DimensionedValue)}, 395 * nor that of 396 * {@link #baseValue()}. 397 * 398 * @param unit The new unit. 399 */ 400 public void setUnit( D unit ); 401 402 /** 403 * Creates a new instance with the sum of this and the given value, and 404 * returns that. 405 * 406 * @param unit The unit for the new instance. 407 * @param summand The value to add. 408 * @return The instance with the sum. 409 */ 410 public default DimensionedValue<D> sum( final D unit, final DimensionedValue<D> summand ) 411 { 412 final var retValue = sum( summand ); 413 retValue.setUnit( unit ); 414 415 //---* Done *---------------------------------------------------------- 416 return retValue; 417 } // sum() 418 419 /** 420 * Creates a new instance with the sum of this and the given value, and 421 * returns that. The unit for the new instance is that of this instance. 422 * 423 * @param summand The value to add. 424 * @return The instance with the sum. 425 */ 426 public default DimensionedValue<D> sum( final DimensionedValue<D> summand ) 427 { 428 final var newValue = baseValue().add( requireNonNullArgument( summand, "summand" ).baseValue(), MATH_CONTEXT ); 429 final var retValue = newInstance( baseUnit(), newValue ); 430 431 //---* Done *---------------------------------------------------------- 432 return retValue; 433 } // sum() 434 435 /** 436 * <p>{@summary Returns the String representation for this value}; 437 * usually, this is in the format</p> 438 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 439 * <p>like "{@code 4.5 m}".</p> 440 * <p>The precision for the mantissa is 441 * provided by the 442 * {@linkplain Dimension#getPrecision() unit}.</p> 443 * <p>If more control over the output format is required, see 444 * {@link #toString(int, int)}.</p> 445 */ 446 @Override 447 public String toString(); 448 449 /** 450 * <p>{@summary Provides a String representation of this value}, in the 451 * format</p> 452 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 453 * <p>for the given 454 * {@link Locale} 455 * that determines the decimal separator, like "{@code 4.5 m}" 456 * vs. "{@code 4,5 m}".</p> 457 * <p>The precision is applied to the numerical part only. The width 458 * includes the 459 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 460 * 461 * @param locale The locale to use. 462 * @param width The minimum number of characters to be written to the 463 * output. If the length of the converted value is less than the width 464 * then the output will be padded by ' ' until the total number 465 * of characters equals width. The padding is at the beginning, as 466 * numerical values are usually right justified. If {@code width} is 467 * -1 then there is no minimum. 468 * @param precision – The number of digits for the mantissa of the value. 469 * If {@code precision} is -1 then there is no explicit limit on the 470 * size of the mantissa. 471 * @return The String representation for this value. 472 */ 473 public default String toString( final Locale locale, final int width, final int precision ) 474 { 475 final var retValue = toString( locale, width, precision, false ); 476 477 //---* Done *---------------------------------------------------------- 478 return retValue; 479 } // toString() 480 481 /** 482 * <p>{@summary Provides a String representation of this value}, in the 483 * format</p> 484 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 485 * <p>for the given 486 * {@link Locale} 487 * that determines the decimal separator, like "{@code 4.5 m}" 488 * vs. "{@code 4,5 m}".</p> 489 * <p>The precision is applied to the numerical part only. The width 490 * includes the 491 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 492 * 493 * @param locale The locale to use. 494 * @param width The minimum number of characters to be written to the 495 * output. If the length of the converted value is less than the width 496 * then the output will be padded by ' ' until the total number 497 * of characters equals width. The padding is at the beginning, as 498 * numerical values are usually right justified. If {@code width} is 499 * -1 then there is no minimum. 500 * @param precision – The number of digits for the mantissa of the value. 501 * If {@code precision} is -1 then there is no explicit limit on the 502 * size of the mantissa. 503 * @param useNiceUnit {@true} if the method 504 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 505 * should be used to retrieve the unit symbol, {@false} if the 506 * usual one is sufficient. 507 * @return The String representation for this value. 508 */ 509 public default String toString( final Locale locale, final int width, final int precision, final boolean useNiceUnit ) 510 { 511 final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol(); 512 final var effectiveWidth = width - unitSymbol.length() - 1; 513 514 final var format = new StringBuilder( "%" ); 515 if( effectiveWidth > 0 ) format.append( effectiveWidth ); 516 if( precision >= 0 ) format.append( "." ).append( precision ); 517 format.append( "f %s" ); 518 519 final var retValue = format( requireNonNullArgument( locale, "locale" ), format.toString(), value(), unitSymbol ); 520 521 //---* Done *---------------------------------------------------------- 522 return retValue; 523 } // toString() 524 525 /** 526 * <p>{@summary Provides a String representation of this value}, in the 527 * format</p> 528 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 529 * <p>and for the 530 * {@linkplain Locale#getDefault() default Locale}, 531 * like "{@code 4.5 m}", where the Locale determines the decimal 532 * separator.</p> 533 * <p>The precision is applied to the numerical part only. The width 534 * includes the 535 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 536 * 537 * @param width The minimum number of characters to be written to the 538 * output. If the length of the converted value is less than the width 539 * then the output will be padded by ' ' until the total number 540 * of characters equals width. The padding is at the beginning, as 541 * numerical values are usually right justified. If {@code width} is 542 * -1 then there is no minimum. 543 * @param precision – The number of digits for the mantissa of the value. 544 * If {@code precision} is -1 then there is no explicit limit on the 545 * size of the mantissa. 546 * @return The String representation for this value. 547 */ 548 public default String toString( final int width, final int precision ) 549 { 550 final var retValue = toString( Locale.getDefault(), width, precision ); 551 552 //---* Done *---------------------------------------------------------- 553 return retValue; 554 } // toString() 555 556 /** 557 * <p>{@summary Provides a String representation of this value}, in the 558 * format that is defined by the provided format String.</p> 559 * <p>That format String must contain exactly one '%f' tag and one '%s' 560 * tag; the first takes the numerical value, the second the unit.</p> 561 * <p>The provided 562 * {@link Locale} 563 * determines the decimal separator and the optional thousands' 564 * separator.</p> 565 * 566 * @param locale The locale to use. 567 * @param format The format String. 568 * @param useNiceUnit {@true} if the method 569 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 570 * should be used to retrieve the unit symbol, {@false} if the 571 * usual one is sufficient. 572 * @return The String representation for this value. 573 * @throws IllegalFormatException The provided format String is invalid. 574 * 575 * @see Formatter 576 */ 577 public default String toString( final Locale locale, final String format, final boolean useNiceUnit ) throws IllegalFormatException 578 { 579 final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol(); 580 final var retValue = format( requireNonNullArgument( locale, "locale" ), requireNotEmptyArgument( format, "format" ), value(), unitSymbol ); 581 582 //---* Done *---------------------------------------------------------- 583 return retValue; 584 } // toString() 585 586 /** 587 * Returns the numerical value. 588 * 589 * @return The numerical value, based on current dimension. 590 */ 591 public default BigDecimal value() { return convert( getUnit() ); } 592} 593// interface DimensionedValue 594 595/* 596 * End of File 597 */