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.lang.value;
019
020import org.apiguardian.api.API;
021import org.tquadrat.foundation.annotation.ClassVersion;
022import org.tquadrat.foundation.exception.UnexpectedExceptionError;
023
024import java.io.Serializable;
025import java.lang.reflect.Constructor;
026import java.lang.reflect.InvocationTargetException;
027import java.math.BigDecimal;
028import java.math.MathContext;
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.math.RoundingMode.HALF_EVEN;
036import static java.util.FormattableFlags.LEFT_JUSTIFY;
037import static org.apiguardian.api.API.Status.STABLE;
038import static org.tquadrat.foundation.lang.Objects.isNull;
039import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
040import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
041
042/**
043 *  <p>{@summary The definition for a value with a dimension.}</p>
044 *  <p>Although the unit for the dimension may be changed, instances of
045 *  classes implementing this interface can be assumed to be immutable as the
046 *  <i>value</i> remains always the same. So at least the results of the
047 *  methods
048 *  {@link #equals(Object)},
049 *  {@link #hashCode()},
050 *  and
051 *  {@link #compareTo(DimensionedValue)}
052 *  will remain always the same
053 *  (while the results from
054 *  {@link #toString()}
055 *  may differ after a call to
056 *  {@link #setUnit(Dimension)}).</p>
057 *  <p>All concrete (non-abstract) implementations of this interface should be
058 *  {@code final}.</p>
059 *
060 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
061 *  @version $Id: DimensionedValue.java 1195 2026-04-15 21:33:40Z tquadrat $
062 *  @since 0.15.1
063 *
064 *  @param  <D> The dimension.
065 *
066 *  @UMLGraph.link
067 */
068@SuppressWarnings( "ClassWithTooManyMethods" )
069@ClassVersion( sourceVersion = "$Id: DimensionedValue.java 1195 2026-04-15 21:33:40Z tquadrat $" )
070@API( status = STABLE, since = "0.25.1" )
071public sealed interface DimensionedValue<D extends Dimension> extends Cloneable, Comparable<DimensionedValue<D>>, Formattable, Serializable
072    permits ValueBase
073{
074        /*-----------*\
075    ====** Constants **========================================================
076        \*-----------*/
077    /**
078     *  The
079     *  {@link MathContext}
080     *  that is used for all the operations with dimensioned values.
081     */
082    public static final MathContext MATH_CONTEXT = new MathContext( 128, HALF_EVEN );
083
084        /*---------*\
085    ====** Methods **==========================================================
086        \*---------*/
087    /**
088     *  Returns the base unit of the dimension for the value.
089     *
090     *  @return The base unit.
091     */
092    public default D baseUnit()
093    {
094        @SuppressWarnings( "unchecked" )
095        final var retValue = (D) getUnit().baseUnit();
096
097        //---* Done *----------------------------------------------------------
098        return retValue;
099    }   //  baseUnit()
100
101    /**
102     *  <p>{@summary Returns the base value (this value, converted to the base
103     *  unit).}</p>
104     *  <p>According to the result, this is the same as calling</p>
105     *  <div class="source-container"><pre>convert( baseUnit() );</pre></div>
106     *
107     *  @return The numerical value as for the base unit.
108     *
109     *  @see #convert(Dimension)
110     */
111    public BigDecimal baseValue();
112
113    /**
114     *  Creates a new copy of this value.
115     *
116     *  @return The copy.
117     */
118    public DimensionedValue<D> clone();
119
120    /**
121     *  {@inheritDoc}
122     *  <p>The comparison is made based on the
123     *  {@link #baseValue()}.</p>
124     */
125    @Override
126    public default int compareTo( final DimensionedValue<D> other )
127    {
128        final var retValue = Integer.signum( baseValue().compareTo( requireNonNullArgument( other, "other" ).baseValue() ) );
129
130        //---* Done *----------------------------------------------------------
131        return retValue;
132    }   //  compareTo()
133
134    /**
135     *  Converts this value to the given unit and returns the numerical value.
136     *
137     *  @param  unit    The unit.
138     *  @return The numerical value of this instance, based on the provided
139     *      unit.
140     *
141     *  @see #baseValue()
142     */
143    public default BigDecimal convert( final D unit )
144    {
145        final var conversion = requireNonNullArgument( unit, "unit" ).fromBase();
146        final var retValue = conversion.apply( baseValue() ).stripTrailingZeros();
147
148        //---* Done *----------------------------------------------------------
149        return retValue;
150    }   //  convert()
151
152    /**
153     *  Creates a new copy of this value.
154     *
155     *  @return The copy.
156     *
157     *  @see Object#clone()
158     */
159    public DimensionedValue<D> copy();
160
161    /**
162     *  Creates a new copy of this value.
163     *
164     *  @param  unit    The unit for the new copy.
165     *  @return The copy.
166     *
167     *  @see Object#clone()
168     */
169    public DimensionedValue<D> copy( final D unit );
170
171    /**
172     *  Divides the value by a dimension-less value and returns the result
173     *  without changing this instance.
174     *
175     *  @param  divisor The divisor.
176     *  @return The new value.
177     */
178    public default DimensionedValue<D> divide( final BigDecimal divisor )
179    {
180        final var retValue = newInstance( baseUnit(), baseValue().divide( requireNonNullArgument( divisor, "divisor" ), MATH_CONTEXT ) );
181        retValue.setUnit( getUnit() );
182
183        //---* Done *----------------------------------------------------------
184        return retValue;
185    }   //  divide()
186
187    /**
188     *  Divides the value by a dimension-less value and returns the result
189     *  without changing this instance.
190     *
191     *  @param  divisor The divisor.
192     *  @return The new value.
193     */
194    public default DimensionedValue<D> divide( final Number divisor )
195    {
196        final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ).toString() ) );
197        retValue.setUnit( getUnit() );
198
199        //---* Done *----------------------------------------------------------
200        return retValue;
201    }   //  divide()
202
203    /**
204     *  Divides the value by a dimension-less value and returns the result
205     *  without changing this instance.
206     *
207     *  @param  divisor The divisor; it must be possible to parse the given
208     *      String into a
209     *      {@link BigDecimal}.
210     *  @return The new value.
211     *  @throws NumberFormatException   The provided value cannot be converted
212     *      into a {@code BigDecimal}.
213     *
214     *  @see BigDecimal#BigDecimal(String)
215     */
216    public default DimensionedValue<D> divide( final String divisor )
217    {
218        final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ) ) );
219
220        //---* Done *----------------------------------------------------------
221        return retValue;
222    }   //  divide()
223
224    /**
225     *  {@inheritDoc}
226     *  <p>Two instances of a class implementing this interface are equals if
227     *  they are of the <i>same</i> class and if their values, converted to the
228     *  base dimension, are equals.</p>
229     *
230     *  @param  o   The other value.
231     *  @return {@code true} if they are equal, {@code false} if not.
232     *
233     *  @see Dimension#baseUnit()
234     */
235    @Override
236    public boolean equals( final Object o );
237
238    /**
239     *  {@inheritDoc}
240     *  <p>The precision is applied to the numerical part only. The width
241     *  includes the
242     *  {@linkplain Dimension#unitSymbol() unit symbol},
243     *  too.</p>
244     *
245     *  @note In case the {@code formatter} argument is {@code null}, this
246     *      method throws a {@code NullPointerException} and <i>not</i> the
247     *      usual {@code NullArgumentException}, because this method is usually
248     *      called by instances of {@code java.util.Formatter}, and those do
249     *      not know about our special exceptions.
250     *
251     *  @throws NullPointerException    The {@code formatter} argument is
252     *      {@code null}.
253     *
254     *  @see Formatter
255     */
256    @SuppressWarnings( "ProhibitedExceptionThrown" )
257    @Override
258    public default void formatTo( final Formatter formatter, final int flags, final int width, final int precision )
259    {
260        if( isNull( formatter ) ) throw new NullPointerException( "formatter is null" );
261
262        var string = toString( width, precision ).trim();
263        final var len = string.length();
264
265        if( ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) && (width > len) )
266        {
267            string = string.concat( " ".repeat( width - len ) );
268        }
269
270        /*
271         * We do not use Formatter.out().append() because we do not know how to
272         * handle the IOException that could be thrown from
273         * Appendable.append(). Using Formatter.format() assumes that Formatter
274         * knows ...
275         */
276        formatter.format( "%s", string );
277    }   //  formatTo()
278
279    /**
280     *  Returns the unit for the value.
281     *
282     *  @return The unit.
283     */
284    public D getUnit();
285
286    /**
287     *  {@inheritDoc}
288     *  <p>The hash code is based on the
289     *  {@linkplain #baseValue() base value}
290     *  and
291     *  {@linkplain #baseUnit() base unit}
292     *  only.</p>
293     */
294    @Override
295    public int hashCode();
296
297    /**
298     *  Multiplies the value by a dimension-less value and returns the result
299     *  without changing this instance.
300     *
301     *  @param  multiplicand The multiplier.
302     *  @return The new value.
303     */
304    public default DimensionedValue<D> multiply( final BigDecimal multiplicand )
305    {
306        final var retValue = newInstance( baseUnit(), baseValue().multiply( requireNonNullArgument( multiplicand, "multiplicand" ) ) );
307        retValue.setUnit( getUnit() );
308
309        //---* Done *----------------------------------------------------------
310        return retValue;
311    }   //  multiply()
312
313    /**
314     *  Multiplies the value by a dimension-less value and returns the result
315     *  without changing this instance.
316     *
317     *  @param  multiplicand The multiplier.
318     *  @return The new value.
319     */
320    public default DimensionedValue<D> multiply( final Number multiplicand )
321    {
322        final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ).toString() ) );
323        retValue.setUnit( getUnit() );
324
325        //---* Done *----------------------------------------------------------
326        return retValue;
327    }   //  multiply()
328
329    /**
330     *  Multiplies the value by a dimension-less value and returns the result
331     *  without changing this instance.
332     *
333     *  @param  multiplicand The multiplier; it must be possible to parse the
334     *      given String into a
335     *      {@link BigDecimal}.
336     *  @return The new value.
337     *  @throws NumberFormatException   The provided value cannot be converted
338     *      into a {@code BigDecimal}.
339     *
340     *  @see BigDecimal#BigDecimal(String)
341     */
342    public default DimensionedValue<D> multiply( final String multiplicand )
343    {
344        final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ) ) );
345
346        //---* Done *----------------------------------------------------------
347        return retValue;
348    }   //  multiply()
349
350    /**
351     *  Creates an instance for the class.
352     *
353     *  @param  dimension   The dimension for the new instance.
354     *  @param  value   The value for the new instance.
355     *  @return The new instance.
356     */
357    public default DimensionedValue<D> newInstance( final D dimension, final BigDecimal value )
358    {
359        @SuppressWarnings( "UnusedAssignment" )
360        DimensionedValue<D> retValue = null;
361
362        try
363        {
364            //---* Let's get the constructor *---------------------------------
365            final Constructor<DimensionedValue<D>> constructor;
366            if( requireNonNullArgument( dimension, "dimension" ) instanceof final Enum<?> enumConstant )
367            {
368                final var enumClass = enumConstant.getDeclaringClass();
369                //noinspection unchecked
370                constructor = (Constructor<DimensionedValue<D>>) getClass().getConstructor( enumClass, BigDecimal.class );
371            }
372            else
373            {
374                throw new IllegalArgumentException( "Invalid type for 'dimension': %s".formatted( dimension.getClass().getName() ) );
375            }
376
377            //---* Create the new value *----------------------------------
378            retValue = constructor.newInstance( dimension, requireNonNullArgument( value, "value" ) );
379        }
380        catch( final NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException e )
381        {
382            throw new UnexpectedExceptionError( e );
383        }
384
385        //---* Done *----------------------------------------------------------
386        return retValue;
387    }   //  newInstance()
388
389    /**
390     *  Applies another unit for the value. This does not affect the results
391     *  of
392     *  {@link #equals(Object)},
393     *  {@link #hashCode()}}
394     *  and
395     *  {@link #compareTo(DimensionedValue)},
396     *  nor that of
397     *  {@link #baseValue()}.
398     *
399     *  @param  unit    The new unit.
400     */
401    public void setUnit( D unit );
402
403    /**
404     *  Creates a new instance with the sum of this and the given value, and
405     *  returns that.
406     *
407     *  @param  unit    The unit for the new instance.
408     *  @param  summand The value to add.
409     *  @return The instance with the sum.
410     */
411    public default DimensionedValue<D> sum( final D unit, final DimensionedValue<D> summand )
412    {
413        final var retValue = sum( summand );
414        retValue.setUnit( unit );
415
416        //---* Done *----------------------------------------------------------
417        return retValue;
418    }   //  sum()
419
420    /**
421     *  Creates a new instance with the sum of this and the given value, and
422     *  returns that. The unit for the new instance is that of this instance.
423     *
424     *  @param  summand The value to add.
425     *  @return The instance with the sum.
426     */
427    public default DimensionedValue<D> sum( final DimensionedValue<D> summand )
428    {
429        final var newValue = baseValue().add( requireNonNullArgument( summand, "summand" ).baseValue(), MATH_CONTEXT );
430        final var retValue = newInstance( baseUnit(), newValue );
431
432        //---* Done *----------------------------------------------------------
433        return retValue;
434    }   // sum()
435
436    /**
437     *  <p>{@summary Returns the String representation for this value};
438     *  usually, this is in the format</p>
439     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
440     *  <p>like &quot;{@code 4.5 m}&quot;.</p>
441     *  <p>The precision for the mantissa is
442     *  provided by the
443     *  {@linkplain Dimension#getPrecision() unit}.</p>
444     *  <p>If more control over the output format is required, see
445     *  {@link #toString(int, int)}.</p>
446     */
447    @Override
448    public String toString();
449
450    /**
451     *  <p>{@summary Provides a String representation of this value}, in the
452     *  format</p>
453     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
454     *  <p>for the given
455     *  {@link Locale}
456     *  that determines the decimal separator, like &quot;{@code 4.5 m}&quot;
457     *  vs. &quot;{@code 4,5 m}&quot;.</p>
458     *  <p>The precision is applied to the numerical part only. The width
459     *  includes the
460     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
461     *
462     *  @param  locale  The locale to use.
463     *  @param  width   The minimum number of characters to be written to the
464     *      output. If the length of the converted value is less than the width
465     *      then the output will be padded by '&nbsp;' until the total number
466     *      of characters equals width. The padding is at the beginning, as
467     *      numerical values are usually right justified. If {@code width} is
468     *      -1 then there is no minimum.
469     *  @param  precision – The number of digits for the mantissa of the value.
470     *      If {@code precision} is -1 then there is no explicit limit on the
471     *      size of the mantissa.
472     *  @return The String representation for this value.
473     */
474    public default String toString( final Locale locale, final int width, final int precision )
475    {
476        final var retValue = toString( locale, width, precision, false );
477
478        //---* Done *----------------------------------------------------------
479        return retValue;
480    }   //  toString()
481
482    /**
483     *  <p>{@summary Provides a String representation of this value}, in the
484     *  format</p>
485     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
486     *  <p>for the given
487     *  {@link Locale}
488     *  that determines the decimal separator, like &quot;{@code 4.5 m}&quot;
489     *  vs. &quot;{@code 4,5 m}&quot;.</p>
490     *  <p>The precision is applied to the numerical part only. The width
491     *  includes the
492     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
493     *
494     *  @param  locale  The locale to use.
495     *  @param  width   The minimum number of characters to be written to the
496     *      output. If the length of the converted value is less than the width
497     *      then the output will be padded by '&nbsp;' until the total number
498     *      of characters equals width. The padding is at the beginning, as
499     *      numerical values are usually right justified. If {@code width} is
500     *      -1 then there is no minimum.
501     *  @param  precision – The number of digits for the mantissa of the value.
502     *      If {@code precision} is -1 then there is no explicit limit on the
503     *      size of the mantissa.
504     *  @param  useNiceUnit {@code true} if the method
505     *      {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()}
506     *      should be used to retrieve the unit symbol, {@code false} if the
507     *      usual one is sufficient.
508     *  @return The String representation for this value.
509     */
510    public default String toString( final Locale locale, final int width, final int precision, final boolean useNiceUnit )
511    {
512        final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol();
513        final var effectiveWidth = width - unitSymbol.length() - 1;
514
515        final var format = new StringBuilder( "%" );
516        if( effectiveWidth > 0 ) format.append( effectiveWidth );
517        if( precision >= 0 ) format.append( "." ).append( precision );
518        format.append( "f %s" );
519
520        final var retValue = format( requireNonNullArgument( locale, "locale" ), format.toString(), value(), unitSymbol );
521
522        //---* Done *----------------------------------------------------------
523        return retValue;
524    }   //  toString()
525
526    /**
527     *  <p>{@summary Provides a String representation of this value}, in the
528     *  format</p>
529     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
530     *  <p>and for the
531     *  {@linkplain Locale#getDefault() default Locale},
532     *  like &quot;{@code 4.5 m}&quot;, where the Locale determines the decimal
533     *  separator.</p>
534     *  <p>The precision is applied to the numerical part only. The width
535     *  includes the
536     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
537     *
538     *  @param  width   The minimum number of characters to be written to the
539     *      output. If the length of the converted value is less than the width
540     *      then the output will be padded by '&nbsp;' until the total number
541     *      of characters equals width. The padding is at the beginning, as
542     *      numerical values are usually right justified. If {@code width} is
543     *      -1 then there is no minimum.
544     *  @param  precision – The number of digits for the mantissa of the value.
545     *      If {@code precision} is -1 then there is no explicit limit on the
546     *      size of the mantissa.
547     *  @return The String representation for this value.
548     */
549    public default String toString( final int width, final int precision )
550    {
551        final var retValue = toString( Locale.getDefault(), width, precision );
552
553        //---* Done *----------------------------------------------------------
554        return retValue;
555    }   //  toString()
556
557    /**
558     *  <p>{@summary Provides a String representation of this value}, in the
559     *  format that is defined by the provided format String.</p>
560     *  <p>That format String must contain exactly one '%f' tag and one '%s'
561     *  tag; the first takes the numerical value, the second the unit.</p>
562     *  <p>The provided
563     *  {@link Locale}
564     *  determines the decimal separator and the optional thousands'
565     *  separator.</p>
566     *
567     *  @param  locale  The locale to use.
568     *  @param  format  The format String.
569     *  @param  useNiceUnit {@code true} if the method
570     *      {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()}
571     *      should be used to retrieve the unit symbol, {@code false} if the
572     *      usual one is sufficient.
573     *  @return The String representation for this value.
574     *  @throws IllegalFormatException  The provided format String is invalid.
575     *
576     *  @see Formatter
577     */
578    public default String toString( final Locale locale, final String format, final boolean useNiceUnit ) throws IllegalFormatException
579    {
580        final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol();
581        final var retValue = format( requireNonNullArgument( locale, "locale" ), requireNotEmptyArgument( format, "format" ), value(), unitSymbol );
582
583        //---* Done *----------------------------------------------------------
584        return retValue;
585    }   //  toString()
586
587    /**
588     *  Returns the numerical value.
589     *
590     *  @return The numerical value, based on current dimension.
591     */
592    public default BigDecimal value() { return convert( getUnit() ); }
593}
594//  interface DimensionedValue
595
596/*
597 *  End of File
598 */