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.UnlikelyExceptionError; 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 * @author Thomas Thrien - thomas.thrien@tquadrat.org 061 * @version $Id: DimensionedValue.java 1295 2026-10-18 12:34:27Z 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 1295 2026-10-18 12:34:27Z 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 * <p>{@summary The 079 * {@link MathContext} 080 * that is used for all the operations with dimensioned values.}</p> 081 */ 082 public static final MathContext MATH_CONTEXT = new MathContext( 128, HALF_EVEN ); 083 084 /*---------*\ 085 ====** Methods **========================================================== 086 \*---------*/ 087 /** 088 * <p>{@summary Returns the base unit of the dimension for the value.}</p> 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 * <p>{@summary Creates a new copy of this value.}</p> 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 * <p>{@summary Converts this value to the given unit and returns the 136 * numerical value.}</p> 137 * 138 * @param unit The unit. 139 * @return The numerical value of this instance, based on the provided 140 * unit. 141 * 142 * @see #baseValue() 143 */ 144 public default BigDecimal convert( final D unit ) 145 { 146 final var conversion = requireNonNullArgument( unit, "unit" ).fromBase(); 147 final var retValue = conversion.apply( baseValue() ).stripTrailingZeros(); 148 149 //---* Done *---------------------------------------------------------- 150 return retValue; 151 } // convert() 152 153 /** 154 * <p>{@summary Creates a new copy of this value.}</p> 155 * 156 * @return The copy. 157 * 158 * @see Object#clone() 159 */ 160 public DimensionedValue<D> copy(); 161 162 /** 163 * <p>{@summary Creates a new copy of this value.}</p> 164 * 165 * @param unit The unit for the new copy. 166 * @return The copy. 167 * 168 * @see Object#clone() 169 */ 170 public DimensionedValue<D> copy( final D unit ); 171 172 /** 173 * <p>{@summary Divides the value by a dimension-less value and returns 174 * the result without changing this instance.}</p> 175 * 176 * @param divisor The divisor. 177 * @return The new value. 178 */ 179 public default DimensionedValue<D> divide( final BigDecimal divisor ) 180 { 181 final var retValue = newInstance( baseUnit(), baseValue().divide( requireNonNullArgument( divisor, "divisor" ), MATH_CONTEXT ) ); 182 retValue.setUnit( getUnit() ); 183 184 //---* Done *---------------------------------------------------------- 185 return retValue; 186 } // divide() 187 188 /** 189 * <p>{@summary Divides the value by a dimension-less value and returns 190 * the result without changing this instance.}</p> 191 * 192 * @param divisor The divisor. 193 * @return The new value. 194 */ 195 public default DimensionedValue<D> divide( final Number divisor ) 196 { 197 final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ).toString() ) ); 198 retValue.setUnit( getUnit() ); 199 200 //---* Done *---------------------------------------------------------- 201 return retValue; 202 } // divide() 203 204 /** 205 * <p>{@summary Divides the value by a dimension-less value and returns 206 * the result without changing this instance.}</p> 207 * 208 * @param divisor The divisor; it must be possible to parse the given 209 * String into a 210 * {@link BigDecimal}. 211 * @return The new value. 212 * @throws NumberFormatException The provided value cannot be converted 213 * into a {@code BigDecimal}. 214 * 215 * @see BigDecimal#BigDecimal(String) 216 */ 217 public default DimensionedValue<D> divide( final String divisor ) 218 { 219 final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ) ) ); 220 221 //---* Done *---------------------------------------------------------- 222 return retValue; 223 } // divide() 224 225 /** 226 * {@inheritDoc} 227 * <p>Two instances of a class implementing this interface are equals if 228 * they are of the <i>same</i> class and if their values, converted to the 229 * base dimension, are equals.</p> 230 * 231 * @param o The other value. 232 * @return {@true} if they are equal, {@false} if not. 233 * 234 * @see Dimension#baseUnit() 235 */ 236 @Override 237 public boolean equals( final Object o ); 238 239 /** 240 * {@inheritDoc} 241 * <p>The precision is applied to the numerical part only. The width 242 * includes the 243 * {@linkplain Dimension#unitSymbol() unit symbol}, 244 * too.</p> 245 * 246 * @note In case the {@code formatter} argument is {@null}, this 247 * method throws a {@code NullPointerException} and <i>not</i> the 248 * usual {@code NullArgumentException}, because this method is usually 249 * called by instances of {@code java.util.Formatter}, and those do 250 * not know about our special exceptions. 251 * 252 * @throws NullPointerException The {@code formatter} argument is 253 * {@null}. 254 * 255 * @see Formatter 256 */ 257 @SuppressWarnings( "ProhibitedExceptionThrown" ) 258 @Override 259 public default void formatTo( final Formatter formatter, final int flags, final int width, final int precision ) 260 { 261 if( isNull( formatter ) ) throw new NullPointerException( "formatter is null" ); 262 263 var s = toString( width, precision ).trim(); 264 final var len = s.length(); 265 266 if( ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) && (width > len) ) 267 { 268 s = s.concat( " ".repeat( width - len ) ); 269 } 270 271 /* 272 * We do not use Formatter.out().append() because we do not know how to 273 * handle the IOException that could be thrown from 274 * Appendable.append(). Using Formatter.format() assumes that Formatter 275 * knows ... 276 */ 277 formatter.format( "%s", s ); 278 } // formatTo() 279 280 /** 281 * <p>{@summary Returns the unit for the value.}</p> 282 * 283 * @return The unit. 284 */ 285 public D getUnit(); 286 287 /** 288 * {@inheritDoc} 289 * <p>The hash code is based on the 290 * {@linkplain #baseValue() base value} 291 * and 292 * {@linkplain #baseUnit() base unit} 293 * only.</p> 294 */ 295 @Override 296 public int hashCode(); 297 298 /** 299 * <p>{@summary Multiplies the value by a dimension-less value and returns 300 * the result without changing this instance.}</p> 301 * 302 * @param multiplicand The multiplier. 303 * @return The new value. 304 */ 305 public default DimensionedValue<D> multiply( final BigDecimal multiplicand ) 306 { 307 final var retValue = newInstance( baseUnit(), baseValue().multiply( requireNonNullArgument( multiplicand, "multiplicand" ) ) ); 308 retValue.setUnit( getUnit() ); 309 310 //---* Done *---------------------------------------------------------- 311 return retValue; 312 } // multiply() 313 314 /** 315 * <p>{@summary Multiplies the value by a dimension-less value and returns 316 * the result without changing this instance.}</p> 317 * 318 * @param multiplicand The multiplier. 319 * @return The new value. 320 */ 321 public default DimensionedValue<D> multiply( final Number multiplicand ) 322 { 323 final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ).toString() ) ); 324 retValue.setUnit( getUnit() ); 325 326 //---* Done *---------------------------------------------------------- 327 return retValue; 328 } // multiply() 329 330 /** 331 * <p>{@summary Multiplies the value by a dimension-less value and returns the result 332 * without changing this instance.}</p> 333 * 334 * @param multiplicand The multiplier; it must be possible to parse the 335 * given String into a 336 * {@link BigDecimal}. 337 * @return The new value. 338 * @throws NumberFormatException The provided value cannot be converted 339 * into a {@code BigDecimal}. 340 * 341 * @see BigDecimal#BigDecimal(String) 342 */ 343 public default DimensionedValue<D> multiply( final String multiplicand ) 344 { 345 final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ) ) ); 346 347 //---* Done *---------------------------------------------------------- 348 return retValue; 349 } // multiply() 350 351 /** 352 * <p>{@summary Creates an instance for the class.}</p> 353 * 354 * @param dimension The dimension for the new instance. 355 * @param value The value for the new instance. 356 * @return The new instance. 357 */ 358 public default DimensionedValue<D> newInstance( final D dimension, final BigDecimal value ) 359 { 360 final DimensionedValue<D> retValue; 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 UnlikelyExceptionError( e ); 383 } 384 385 //---* Done *---------------------------------------------------------- 386 return retValue; 387 } // newInstance() 388 389 /** 390 * <p>{@summary Applies another unit for the value.} This does not affect 391 * the results of 392 * {@link #equals(Object)}, 393 * {@link #hashCode()}} 394 * and 395 * {@link #compareTo(DimensionedValue)}, 396 * nor that of 397 * {@link #baseValue()}.</p> 398 * 399 * @param unit The new unit. 400 */ 401 public void setUnit( D unit ); 402 403 /** 404 * <p>{@summary Creates a new instance with the sum of this and the given 405 * value, and returns that.}</p> 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 * <p>{@summary Creates a new instance with the sum of this and the given 422 * value, and returns that. The unit for the new instance is that of this 423 * instance.}</p> 424 * 425 * @param summand The value to add. 426 * @return The instance with the sum. 427 */ 428 public default DimensionedValue<D> sum( final DimensionedValue<D> summand ) 429 { 430 final var newValue = baseValue().add( requireNonNullArgument( summand, "summand" ).baseValue(), MATH_CONTEXT ); 431 final var retValue = newInstance( baseUnit(), newValue ); 432 433 //---* Done *---------------------------------------------------------- 434 return retValue; 435 } // sum() 436 437 /** 438 * <p>{@summary Returns the String representation for this value}; 439 * usually, this is in the format</p> 440 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 441 * <p>like "{@code 4.5 m}".</p> 442 * <p>The precision for the mantissa is 443 * provided by the 444 * {@linkplain Dimension#getPrecision() unit}.</p> 445 * <p>If more control over the output format is required, see 446 * {@link #toString(int, int)}.</p> 447 */ 448 @Override 449 public String toString(); 450 451 /** 452 * <p>{@summary Provides a String representation of this value}, in the 453 * format</p> 454 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 455 * <p>for the given 456 * {@link Locale} 457 * that determines the decimal separator, like "{@code 4.5 m}" 458 * vs. "{@code 4,5 m}".</p> 459 * <p>The precision is applied to the numerical part only. The width 460 * includes the 461 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 462 * 463 * @param locale The locale to use. 464 * @param width The minimum number of characters to be written to the 465 * output. If the length of the converted value is less than the width 466 * then the output will be padded by ' ' until the total number 467 * of characters equals width. The padding is at the beginning, as 468 * numerical values are usually right justified. If {@code width} is 469 * -1 then there is no minimum. 470 * @param precision – The number of digits for the mantissa of the value. 471 * If {@code precision} is -1 then there is no explicit limit on the 472 * size of the mantissa. 473 * @return The String representation for this value. 474 */ 475 public default String toString( final Locale locale, final int width, final int precision ) 476 { 477 final var retValue = toString( locale, width, precision, false ); 478 479 //---* Done *---------------------------------------------------------- 480 return retValue; 481 } // toString() 482 483 /** 484 * <p>{@summary Provides a String representation of this value}, in the 485 * format</p> 486 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 487 * <p>for the given 488 * {@link Locale} 489 * that determines the decimal separator, like "{@code 4.5 m}" 490 * vs. "{@code 4,5 m}".</p> 491 * <p>The precision is applied to the numerical part only. The width 492 * includes the 493 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 494 * 495 * @param locale The locale to use. 496 * @param width The minimum number of characters to be written to the 497 * output. If the length of the converted value is less than the width 498 * then the output will be padded by ' ' until the total number 499 * of characters equals width. The padding is at the beginning, as 500 * numerical values are usually right justified. If {@code width} is 501 * -1 then there is no minimum. 502 * @param precision – The number of digits for the mantissa of the value. 503 * If {@code precision} is -1 then there is no explicit limit on the 504 * size of the mantissa. 505 * @param useNiceUnit {@true} if the method 506 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 507 * should be used to retrieve the unit symbol, {@false} if the 508 * usual one is sufficient. 509 * @return The String representation for this value. 510 */ 511 public default String toString( final Locale locale, final int width, final int precision, final boolean useNiceUnit ) 512 { 513 final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol(); 514 final var effectiveWidth = width - unitSymbol.length() - 1; 515 516 final var format = new StringBuilder( "%" ); 517 if( effectiveWidth > 0 ) format.append( effectiveWidth ); 518 if( precision >= 0 ) format.append( "." ).append( precision ); 519 format.append( "f %s" ); 520 521 final var retValue = format( requireNonNullArgument( locale, "locale" ), format.toString(), value(), unitSymbol ); 522 523 //---* Done *---------------------------------------------------------- 524 return retValue; 525 } // toString() 526 527 /** 528 * <p>{@summary Provides a String representation of this value}, in the 529 * format</p> 530 * <pre><code><<i>numerical value</i>> <<i>unit symbol</i>></code></pre> 531 * <p>and for the 532 * {@linkplain Locale#getDefault() default Locale}, 533 * like "{@code 4.5 m}", where the Locale determines the decimal 534 * separator.</p> 535 * <p>The precision is applied to the numerical part only. The width 536 * includes the 537 * {@linkplain Dimension#unitSymbol() unit symbol}, too.</p> 538 * 539 * @param width The minimum number of characters to be written to the 540 * output. If the length of the converted value is less than the width 541 * then the output will be padded by ' ' until the total number 542 * of characters equals width. The padding is at the beginning, as 543 * numerical values are usually right justified. If {@code width} is 544 * -1 then there is no minimum. 545 * @param precision – The number of digits for the mantissa of the value. 546 * If {@code precision} is -1 then there is no explicit limit on the 547 * size of the mantissa. 548 * @return The String representation for this value. 549 */ 550 public default String toString( final int width, final int precision ) 551 { 552 final var retValue = toString( Locale.getDefault(), width, precision ); 553 554 //---* Done *---------------------------------------------------------- 555 return retValue; 556 } // toString() 557 558 /** 559 * <p>{@summary Provides a String representation of this value}, in the 560 * format that is defined by the provided format String.</p> 561 * <p>That format String must contain exactly one '%f' tag and one '%s' 562 * tag; the first takes the numerical value, the second the unit.</p> 563 * <p>The provided 564 * {@link Locale} 565 * determines the decimal separator and the optional thousands' 566 * separator.</p> 567 * 568 * @param locale The locale to use. 569 * @param format The format String. 570 * @param useNiceUnit {@true} if the method 571 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 572 * should be used to retrieve the unit symbol, {@false} if the 573 * usual one is sufficient. 574 * @return The String representation for this value. 575 * @throws IllegalFormatException The provided format String is invalid. 576 * 577 * @see Formatter 578 */ 579 public default String toString( final Locale locale, final String format, final boolean useNiceUnit ) throws IllegalFormatException 580 { 581 final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol(); 582 final var retValue = format( requireNonNullArgument( locale, "locale" ), requireNotEmptyArgument( format, "format" ), value(), unitSymbol ); 583 584 //---* Done *---------------------------------------------------------- 585 return retValue; 586 } // toString() 587 588 /** 589 * <p>{@summary Returns the numerical value.}</p> 590 * 591 * @return The numerical value, based on current dimension. 592 */ 593 public default BigDecimal value() { return convert( getUnit() ); } 594} 595// interface DimensionedValue 596 597/* 598 * End of File 599 */