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