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>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>currency symbol</i>&gt;</code></pre>
284     *  <p>and for the
285     *  {@linkplain Locale#getDefault() default Locale},
286     *  like &quot;{@code 4.50 €}&quot;, 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 '&nbsp;' 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>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>currency symbol</i>&gt;</code></pre>
315     *  <p>and for the
316     *  {@linkplain Locale#getDefault() default Locale},
317     *  like &quot;{@code 4.50 €}&quot;, 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 '&nbsp;' 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>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>currency symbol</i>&gt;</code></pre>
344     *  <p>for the given
345     *  {@link Locale}
346     *  that determines the decimal separator, like &quot;{@code 4.50 €}&quot;
347     *  vs. &quot;{@code 4,50 €}&quot;.</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 '&nbsp;' 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>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>currency symbol</i>&gt;</code></pre>
376     *  <p>for the given
377     *  {@link Locale}
378     *  that determines the decimal separator, like &quot;{@code 4.50 €}&quot;
379     *  vs. &quot;{@code 4,50 €}&quot;.</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 '&nbsp;' 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>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>currency symbol</i>&gt;</code></pre>
406     *  <p>for the given
407     *  {@link Locale}
408     *  that determines the decimal separator, like &quot;{@code 4.50 €}&quot;
409     *  vs. &quot;{@code 4,50 €}&quot;.</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 '&nbsp;' 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 */