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