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