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.value; 019 020import static java.lang.String.format; 021import static java.util.FormattableFlags.ALTERNATE; 022import static java.util.FormattableFlags.LEFT_JUSTIFY; 023import static java.util.Locale.ROOT; 024import static org.apiguardian.api.API.Status.STABLE; 025import static org.tquadrat.foundation.lang.Objects.hash; 026import static org.tquadrat.foundation.lang.Objects.isNull; 027import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 028import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 029import static org.tquadrat.foundation.lang.value.DimensionedValue.MATH_CONTEXT; 030import static org.tquadrat.foundation.util.StringUtils.padRight; 031 032import java.io.Serial; 033import java.io.Serializable; 034import java.math.BigDecimal; 035import java.util.Currency; 036import java.util.Formattable; 037import java.util.Formatter; 038import java.util.IllegalFormatException; 039import java.util.Locale; 040 041import org.apiguardian.api.API; 042import org.tquadrat.foundation.annotation.ClassVersion; 043import org.tquadrat.foundation.exception.UnlikelyExceptionError; 044import org.tquadrat.foundation.lang.Objects; 045 046/** 047 * <p>{@summary A value type for currency values.}</p> 048 * <p>As there is no constant conversion between currencies, this value type 049 * is not implementing the interface 050 * {@link org.tquadrat.foundation.lang.value.DimensionedValue}.</p> 051 * 052 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 053 * @version $Id: CurrencyValue.java 1258 2026-06-04 18:33:06Z tquadrat $ 054 * @since 0.0.4 055 * 056 * @UMLGraph.link 057 */ 058@SuppressWarnings( "SerializableDeserializableClassInSecureContext" ) 059@ClassVersion( sourceVersion = "$Id: CurrencyValue.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 060@API( status = STABLE, since = "0.0.4" ) 061public final class CurrencyValue implements Cloneable, Comparable<CurrencyValue>, Formattable, Serializable 062{ 063 /*------------*\ 064 ====** Attributes **======================================================= 065 \*------------*/ 066 /** 067 * The unit for the value. 068 * 069 * @serial 070 */ 071 private final Currency m_Unit; 072 073 /** 074 * The numerical value for this instance. 075 * 076 * @serial 077 */ 078 private final BigDecimal m_Value; 079 080 /*------------------------*\ 081 ====** Static Initialisations **=========================================== 082 \*------------------------*/ 083 /** 084 * The serial version UID for objects of this class: {@value}. 085 */ 086 @Serial 087 private static final long serialVersionUID = -2075489505691464486L; 088 089 /*--------------*\ 090 ====** Constructors **===================================================== 091 \*--------------*/ 092 /** 093 * <p>{@summary {@constructor}}</p> 094 * 095 * @param unit The unit. 096 * @param value The value; only absolute (positive) values are allowed, 097 * a sign will be stripped. 098 */ 099 public CurrencyValue( final Currency unit, final BigDecimal value ) 100 { 101 m_Unit = requireNonNullArgument( unit, "unit" ); 102 m_Value = requireNonNullArgument( value, "value" ).abs().stripTrailingZeros(); 103 } // CurrencyValue() 104 105 /** 106 * <p>{@summary {@constructor}}</p> 107 * 108 * @param unit The unit. 109 * @param value The value; it must be possible to parse the given 110 * String into a 111 * {@link BigDecimal}. 112 * @throws NumberFormatException The provided value cannot be converted 113 * into a {@code BigDecimal}. 114 */ 115 public CurrencyValue( final Currency unit, final String value ) throws NumberFormatException 116 { 117 this( unit, new BigDecimal( requireNotEmptyArgument( value, "value" ) ) ); 118 } // CurrencyValue() 119 120 /** 121 * <p>{@summary {@constructor}}</p> 122 * 123 * @param <N> The type of {@code value}. 124 * @param unit The unit. 125 * @param value The value. 126 */ 127 public <N extends Number> CurrencyValue( final Currency unit, final N value ) 128 { 129 this( unit, new BigDecimal( requireNonNullArgument( value, "value" ).toString() ) ); 130 } // CurrencyValue() 131 132 /*---------*\ 133 ====** Methods **========================================================== 134 \*---------*/ 135 /** 136 * <p>{@summary Returns the amount.}</p> 137 * 138 * @return The numerical value for this instance of {@code CurrencyValue}. 139 */ 140 public final BigDecimal baseValue() { return m_Value; } 141 142 /** 143 * {@inheritDoc} 144 */ 145 @Override 146 public final CurrencyValue clone() 147 { 148 final CurrencyValue retValue; 149 try 150 { 151 retValue = (CurrencyValue) super.clone(); 152 } 153 catch( final CloneNotSupportedException e ) 154 { 155 throw new UnlikelyExceptionError( e ); 156 } 157 158 //---* Done *---------------------------------------------------------- 159 return retValue; 160 } // clone() 161 162 /** 163 * {@inheritDoc} 164 * 165 * @throws IllegalArgumentException The currencies for both values are 166 * different. 167 */ 168 @SuppressWarnings( "UseOfConcreteClass" ) 169 @Override 170 public final int compareTo( final CurrencyValue other ) 171 { 172 if( !m_Unit.equals( requireNonNullArgument( other, "other" ).m_Unit ) ) throw new IllegalArgumentException( "Currency differs" ); 173 final var retValue = Integer.signum( m_Value.compareTo( other.m_Value ) ); 174 175 //---* Done *---------------------------------------------------------- 176 return retValue; 177 } // compareTo() 178 179 /** 180 * Creates a new copy of this value. 181 * 182 * @return The copy. 183 * 184 * @see Object#clone() 185 */ 186 public final CurrencyValue copy() 187 { 188 final var retValue = clone(); 189 190 //---* Done *---------------------------------------------------------- 191 return retValue; 192 } // copy() 193 194 /** 195 * Creates a new instance of {@code CurrencyValue} for a different 196 * {@link Currency}. 197 * 198 * @param unit The {@code Currency} for the new value. 199 * @param conversionFactor The value for this instance multiplied with 200 * the given factor results in the value for the instance. 201 * @return The new instance. 202 */ 203 public final CurrencyValue copy( final Currency unit, final BigDecimal conversionFactor ) 204 { 205 final var retValue = new CurrencyValue( requireNonNullArgument( unit, "unit" ), m_Value.multiply( requireNonNullArgument( conversionFactor, "conversionFactor" ), MATH_CONTEXT ) ); 206 207 //---* Done *---------------------------------------------------------- 208 return retValue; 209 } // copy() 210 211 /** 212 * {@inheritDoc} 213 */ 214 @Override 215 public final boolean equals( final Object obj ) 216 { 217 var retValue = this == obj; 218 if( !retValue && (obj instanceof final CurrencyValue other ) ) 219 { 220 retValue = Objects.equals( m_Unit, other.m_Unit ) && Objects.equals( m_Value, other.m_Value ); 221 } 222 223 //---* Done *---------------------------------------------------------- 224 return retValue; 225 } // equals() 226 227 /** 228 * {@inheritDoc} 229 * <p>The precision is applied to the numerical part only. The width 230 * includes the 231 * {@linkplain Currency#getSymbol(Locale) currency symbol}, 232 * too.</p> 233 * 234 * @note In case the {@code formatter} argument is {@null}, this 235 * method throws a {@code NullPointerException} and <i>not</i> the 236 * usual {@code NullArgumentException}, because this method is usually 237 * called by instances of {@code java.util.Formatter}, and those do 238 * not know about our special exceptions. 239 * 240 * @throws NullPointerException The {@code formatter} argument is 241 * {@null}. 242 * 243 * @see java.util.Formatter 244 */ 245 @SuppressWarnings( "ProhibitedExceptionThrown" ) 246 @Override 247 public final void formatTo( final Formatter formatter, final int flags, final int width, final int precision ) 248 { 249 if( isNull( formatter ) ) throw new NullPointerException( "formatter is null" ); 250 251 var s = toString( formatter.locale(), width, precision < 0 ? m_Unit.getDefaultFractionDigits() : precision, (flags & ALTERNATE) == ALTERNATE ); 252 253 if( ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) && (width > s.trim().length()) ) 254 { 255 s = padRight( s.trim(), width ); 256 } 257 258 /* 259 * We do not use Formatter.out().append() because we do not know how to 260 * handle the IOException that could be thrown from 261 * Appendable.append(). Using Formatter.format() assumes that Formatter 262 * knows ... 263 */ 264 formatter.format( "%s", s ); 265 } // formatTo() 266 267 /** 268 * Returns the unit for the value. 269 * 270 * @return The unit. 271 */ 272 public final Currency getUnit() { return m_Unit; } 273 274 /** 275 * {@inheritDoc} 276 */ 277 @Override 278 public final int hashCode() { return hash( m_Value, m_Unit ); } 279 280 /** 281 * <p>{@summary Provides a String representation of this value}, in the 282 * format</p> 283 * <pre><code><<i>numerical value</i>> <<i>currency symbol</i>></code></pre> 284 * <p>and for the 285 * {@linkplain Locale#getDefault() default Locale}, 286 * like "{@code 4.50 €}", where the Locale determines the 287 * decimal separator.</p> 288 * <p>The precision is applied to the numerical part only. The width 289 * includes the 290 * {@linkplain Currency#getSymbol(Locale) currency symbol}, too.</p> 291 * 292 * @param width The minimum number of characters to be written to the 293 * output. If the length of the converted value is less than the width 294 * then the output will be padded by ' ' until the total number 295 * of characters equals width. The padding is at the beginning, as 296 * numerical values are usually right justified. If {@code width} is 297 * -1 then there is no minimum. 298 * @param precision – The number of digits for the mantissa of the value. 299 * If {@code precision} is -1 then there is no explicit limit on the 300 * size of the mantissa. 301 * @return The String representation for this value. 302 */ 303 public final String toString( final int width, final int precision ) 304 { 305 final var retValue = toString( Locale.getDefault(), width, precision ); 306 307 //---* Done *---------------------------------------------------------- 308 return retValue; 309 } // toString() 310 311 /** 312 * <p>{@summary Provides a String representation of this value}, in the 313 * format</p> 314 * <pre><code><<i>numerical value</i>> <<i>currency symbol</i>></code></pre> 315 * <p>and for the 316 * {@linkplain Locale#getDefault() default Locale}, 317 * like "{@code 4.50 €}", where the Locale determines the 318 * decimal separator.</p> 319 * <p>The precision is taken from the 320 * {@link Currency#getDefaultFractionDigits() currency} 321 * and applied to the numerical part only. The width includes the 322 * {@linkplain Currency#getSymbol(Locale) currency symbol}, too.</p> 323 * 324 * @param width The minimum number of characters to be written to the 325 * output. If the length of the converted value is less than the width 326 * then the output will be padded by ' ' until the total number 327 * of characters equals width. The padding is at the beginning, as 328 * numerical values are usually right justified. If {@code width} is 329 * -1 then there is no minimum. 330 * @return The String representation for this value. 331 */ 332 public final String toString( final int width ) 333 { 334 final var retValue = toString( width, m_Unit.getDefaultFractionDigits() ); 335 336 //---* Done *---------------------------------------------------------- 337 return retValue; 338 } // toString() 339 340 /** 341 * <p>{@summary Provides a String representation of this value}, in the 342 * format</p> 343 * <pre><code><<i>numerical value</i>> <<i>currency symbol</i>></code></pre> 344 * <p>for the given 345 * {@link Locale} 346 * that determines the decimal separator, like "{@code 4.50 €}" 347 * vs. "{@code 4,50 €}".</p> 348 * <p>The precision is applied to the numerical part only. The width 349 * includes the 350 * {@linkplain Currency#getSymbol(Locale) currency symbol}, too.</p> 351 * 352 * @param locale The locale to use. 353 * @param width The minimum number of characters to be written to the 354 * output. If the length of the converted value is less than the width 355 * then the output will be padded by ' ' until the total number 356 * of characters equals width. The padding is at the beginning, as 357 * numerical values are usually right justified. If {@code width} is 358 * -1 then there is no minimum. 359 * @param precision – The number of digits for the mantissa of the value. 360 * If {@code precision} is -1 then there is no explicit limit on the 361 * size of the mantissa. 362 * @return The String representation for this value. 363 */ 364 public final String toString( final Locale locale, final int width, final int precision ) 365 { 366 final var retValue = toString( locale, width, precision, false ); 367 368 //---* Done *---------------------------------------------------------- 369 return retValue; 370 } // toString() 371 372 /** 373 * <p>{@summary Provides a String representation of this value}, in the 374 * format</p> 375 * <pre><code><<i>numerical value</i>> <<i>currency symbol</i>></code></pre> 376 * <p>for the given 377 * {@link Locale} 378 * that determines the decimal separator, like "{@code 4.50 €}" 379 * vs. "{@code 4,50 €}".</p> 380 * <p>The precision is taken from the 381 * {@link Currency#getDefaultFractionDigits() currency} 382 * and applied to the numerical part only. The width includes the 383 * {@linkplain Currency#getSymbol(Locale) currency symbol}, too.</p> 384 * 385 * @param locale The locale to use. 386 * @param width The minimum number of characters to be written to the 387 * output. If the length of the converted value is less than the width 388 * then the output will be padded by ' ' until the total number 389 * of characters equals width. The padding is at the beginning, as 390 * numerical values are usually right justified. If {@code width} is 391 * -1 then there is no minimum. 392 * @return The String representation for this value. 393 */ 394 public final String toString( final Locale locale, final int width ) 395 { 396 final var retValue = toString( locale, width, m_Unit.getDefaultFractionDigits() ); 397 398 //---* Done *---------------------------------------------------------- 399 return retValue; 400 } // toString() 401 402 /** 403 * <p>{@summary Provides a String representation of this value}, in the 404 * format</p> 405 * <pre><code><<i>numerical value</i>> <<i>currency symbol</i>></code></pre> 406 * <p>for the given 407 * {@link Locale} 408 * that determines the decimal separator, like "{@code 4.50 €}" 409 * vs. "{@code 4,50 €}".</p> 410 * <p>The precision is applied to the numerical part only. The width 411 * includes the 412 * {@linkplain org.tquadrat.foundation.lang.value.Dimension#unitSymbol() unit symbol}, too.</p> 413 * 414 * @param locale The locale to use. 415 * @param width The minimum number of characters to be written to the 416 * output. If the length of the converted value is less than the width 417 * then the output will be padded by ' ' until the total number 418 * of characters equals width. The padding is at the beginning, as 419 * numerical values are usually right justified. If {@code width} is 420 * -1 then there is no minimum. 421 * @param precision – The number of digits for the mantissa of the value. 422 * If {@code precision} is -1 then there is no explicit limit on the 423 * size of the mantissa. 424 * @param useNiceUnit {@true} if the method 425 * {@link org.tquadrat.foundation.lang.value.Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 426 * should be used to retrieve the unit symbol, {@false} if the 427 * usual one is sufficient. 428 * @return The String representation for this value. 429 */ 430 public final String toString( final Locale locale, final int width, final int precision, final boolean useNiceUnit ) 431 { 432 requireNonNullArgument( locale, "locale" ); 433 final var unitSymbol = useNiceUnit ? m_Unit.getSymbol( locale ) : m_Unit.getCurrencyCode(); 434 final var effectiveWidth = width - unitSymbol.length() - 1; 435 436 final var format = new StringBuilder( "%" ); 437 if( effectiveWidth > 0 ) format.append( effectiveWidth ); 438 if( precision >= 0 ) format.append( "." ).append( precision ); 439 format.append( "f %s" ); 440 441 final var retValue = format( locale, format.toString(), m_Value, unitSymbol ); 442 443 //---* Done *---------------------------------------------------------- 444 return retValue; 445 } // toString() 446 447 /** 448 * <p>{@summary Provides a String representation of this value}, in the 449 * format that is defined by the provided format String.</p> 450 * <p>That format String must contain exactly one '%f' tag and one '%s' 451 * tag; the first takes the numerical value, the second the unit.</p> 452 * <p>The provided 453 * {@link Locale} 454 * determines the decimal separator and the optional thousands' 455 * separator.</p> 456 * 457 * @param locale The locale to use. 458 * @param format The format String. 459 * @param useNiceUnit {@true} if the method 460 * {@link org.tquadrat.foundation.lang.value.Dimension#unitSymbolForPrinting() unitSymbolForPrinting()} 461 * should be used to retrieve the unit symbol, {@false} if the 462 * usual one is sufficient. 463 * @return The String representation for this value. 464 * @throws IllegalFormatException The provided format String is invalid. 465 * 466 * @see java.util.Formatter 467 */ 468 public final String toString( final Locale locale, final String format, final boolean useNiceUnit ) throws IllegalFormatException 469 { 470 requireNonNullArgument( locale, "locale" ); 471 final var unitSymbol = useNiceUnit ? m_Unit.getSymbol( locale ) : m_Unit.getCurrencyCode(); 472 final var retValue = format( locale, requireNotEmptyArgument( format, "format" ), m_Value, unitSymbol ); 473 474 //---* Done *---------------------------------------------------------- 475 return retValue; 476 } // toString() 477 478 /** 479 * {@inheritDoc} 480 */ 481 @Override 482 public final String toString() { return toString( ROOT, -1, m_Unit.getDefaultFractionDigits(), false ); } 483} 484// class ValueBase 485 486/* 487 * End of File 488 */