001/* 002 * ============================================================================ 003 * Copyright © 2002-2023 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.value.api; 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; 027import static org.tquadrat.foundation.util.StringUtils.padRight; 028 029import java.io.Serializable; 030import java.lang.reflect.Constructor; 031import java.lang.reflect.InvocationTargetException; 032import java.math.BigDecimal; 033import java.math.MathContext; 034import java.util.Formattable; 035import java.util.Formatter; 036import java.util.IllegalFormatException; 037import java.util.Locale; 038 039import org.apiguardian.api.API; 040import org.tquadrat.foundation.annotation.ClassVersion; 041import org.tquadrat.foundation.exception.UnexpectedExceptionError; 042 043/** 044 * <p>{@summary The definition for a value with a dimension.}</p> 045 * <p>Although the unit for the dimension may be changed, instances of 046 * classes implementing this interface can be assumed to be immutable as the 047 * <i>value</i> remains always the same. So at least the results of the 048 * methods 049 * {@link #equals(Object)}, 050 * {@link #hashCode()}, 051 * and 052 * {@link #compareTo(DimensionedValue)} 053 * will remain always the same 054 * (while the results from 055 * {@link #toString()} 056 * may differ after a call to 057 * {@link #setUnit(Dimension)}).</p> 058 * <p>All concrete (non-abstract) implementations of this interface should be 059 * {@code final}.</p> 060 * 061 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 062 * @version $Id: DimensionedValue.java 1072 2023-09-30 20:44:38Z tquadrat $ 063 * @since 0.1.0 064 * 065 * @param <D> The dimension. 066 * 067 * @UMLGraph.link 068 */ 069@SuppressWarnings( "ClassWithTooManyMethods" ) 070@ClassVersion( sourceVersion = "$Id: DimensionedValue.java 1072 2023-09-30 20:44:38Z tquadrat $" ) 071@API( status = STABLE, since = "0.1.0" ) 072public sealed interface DimensionedValue<D extends Dimension> extends Cloneable, Comparable<DimensionedValue<D>>, Formattable, Serializable 073 permits ValueBase 074{ 075 /*-----------*\ 076 ====** Constants **======================================================== 077 \*-----------*/ 078 /** 079 * The 080 * {@link MathContext} 081 * that is used for all the operations with dimensioned values. 082 */ 083 public static final MathContext MATH_CONTEXT = new MathContext( 128, HALF_EVEN ); 084 085 /*---------*\ 086 ====** Methods **========================================================== 087 \*---------*/ 088 /** 089 * Returns the base unit of the dimension for the value. 090 * 091 * @return The base unit. 092 */ 093 public default D baseUnit() 094 { 095 @SuppressWarnings( "unchecked" ) 096 final var retValue = (D) getUnit().baseUnit(); 097 098 //---* Done *---------------------------------------------------------- 099 return retValue; 100 } // baseUnit() 101 102 /** 103 * <p>{@summary Returns the base value (this value, converted to the base 104 * unit).}</p> 105 * <p>According to the result, this is the same as calling</p> 106 * <pre><code>convert( baseUnit() );</code></pre>. 107 * 108 * @return The numerical value as for the base unit. 109 * 110 * @see #convert(Dimension) 111 */ 112 public BigDecimal baseValue(); 113 114 /** 115 * Creates a new copy of this value. 116 * 117 * @return The copy. 118 */ 119 public DimensionedValue<D> clone(); 120 121 /** 122 * {@inheritDoc} 123 * <p>The comparison is made based on the 124 * {@link #baseValue()}.</p> 125 */ 126 @Override 127 public default int compareTo( final DimensionedValue<D> other ) 128 { 129 final var retValue = Integer.signum( baseValue().compareTo( requireNonNullArgument( other, "other" ).baseValue() ) ); 130 131 //---* Done *---------------------------------------------------------- 132 return retValue; 133 } // compareTo() 134 135 /** 136 * Converts this value to the given unit and returns the numerical value. 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 * Creates a new copy of this value. 155 * 156 * @return The copy. 157 * 158 * @see Object#clone() 159 */ 160 public DimensionedValue<D> copy(); 161 162 /** 163 * Creates a new copy of this value. 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 * Divides the value by a dimension-less value and returns the result 174 * without changing this instance. 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 * Divides the value by a dimension-less value and returns the result 190 * without changing this instance. 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 * Divides the value by a dimension-less value and returns the result 206 * without changing this instance. 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 {@code true} if they are equal, {@code 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 {@code 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 * {@code null}. 254 * 255 * @see java.util.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 string = toString( width, precision ); 264 265 if( ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) && (width > string.trim().length()) ) 266 { 267 string = padRight( string.trim(), width ); 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 {@code true} if the method 504 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 505 * should be used to retrieve the unit symbol, {@code 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 {@code true} if the method 569 * {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 570 * should be used to retrieve the unit symbol, {@code 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 java.util.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 */