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 static java.lang.String.format;
021import static java.math.RoundingMode.HALF_EVEN;
022import static java.util.FormattableFlags.LEFT_JUSTIFY;
023import static org.apiguardian.api.API.Status.STABLE;
024import static org.tquadrat.foundation.lang.Objects.isNull;
025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
026import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
027
028import java.io.Serializable;
029import java.lang.reflect.Constructor;
030import java.lang.reflect.InvocationTargetException;
031import java.math.BigDecimal;
032import java.math.MathContext;
033import java.util.Formattable;
034import java.util.Formatter;
035import java.util.IllegalFormatException;
036import java.util.Locale;
037
038import org.apiguardian.api.API;
039import org.tquadrat.foundation.annotation.ClassVersion;
040import org.tquadrat.foundation.exception.UnexpectedExceptionError;
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 1286 2026-09-09 09:43:11Z 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 1286 2026-09-09 09:43:11Z 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 {@true} if they are equal, {@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 {@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     *      {@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        DimensionedValue<D> retValue = null;
360
361        try
362        {
363            //---* Let's get the constructor *---------------------------------
364            final Constructor<DimensionedValue<D>> constructor;
365            if( requireNonNullArgument( dimension, "dimension" ) instanceof final Enum<?> enumConstant )
366            {
367                final var enumClass = enumConstant.getDeclaringClass();
368                //noinspection unchecked
369                constructor = (Constructor<DimensionedValue<D>>) getClass().getConstructor( enumClass, BigDecimal.class );
370            }
371            else
372            {
373                throw new IllegalArgumentException( "Invalid type for 'dimension': %s".formatted( dimension.getClass().getName() ) );
374            }
375
376            //---* Create the new value *----------------------------------
377            retValue = constructor.newInstance( dimension, requireNonNullArgument( value, "value" ) );
378        }
379        catch( final NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException e )
380        {
381            throw new UnexpectedExceptionError( e );
382        }
383
384        //---* Done *----------------------------------------------------------
385        return retValue;
386    }   //  newInstance()
387
388    /**
389     *  Applies another unit for the value. This does not affect the results
390     *  of
391     *  {@link #equals(Object)},
392     *  {@link #hashCode()}}
393     *  and
394     *  {@link #compareTo(DimensionedValue)},
395     *  nor that of
396     *  {@link #baseValue()}.
397     *
398     *  @param  unit    The new unit.
399     */
400    public void setUnit( D unit );
401
402    /**
403     *  Creates a new instance with the sum of this and the given value, and
404     *  returns that.
405     *
406     *  @param  unit    The unit for the new instance.
407     *  @param  summand The value to add.
408     *  @return The instance with the sum.
409     */
410    public default DimensionedValue<D> sum( final D unit, final DimensionedValue<D> summand )
411    {
412        final var retValue = sum( summand );
413        retValue.setUnit( unit );
414
415        //---* Done *----------------------------------------------------------
416        return retValue;
417    }   //  sum()
418
419    /**
420     *  Creates a new instance with the sum of this and the given value, and
421     *  returns that. The unit for the new instance is that of this instance.
422     *
423     *  @param  summand The value to add.
424     *  @return The instance with the sum.
425     */
426    public default DimensionedValue<D> sum( final DimensionedValue<D> summand )
427    {
428        final var newValue = baseValue().add( requireNonNullArgument( summand, "summand" ).baseValue(), MATH_CONTEXT );
429        final var retValue = newInstance( baseUnit(), newValue );
430
431        //---* Done *----------------------------------------------------------
432        return retValue;
433    }   // sum()
434
435    /**
436     *  <p>{@summary Returns the String representation for this value};
437     *  usually, this is in the format</p>
438     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
439     *  <p>like &quot;{@code 4.5 m}&quot;.</p>
440     *  <p>The precision for the mantissa is
441     *  provided by the
442     *  {@linkplain Dimension#getPrecision() unit}.</p>
443     *  <p>If more control over the output format is required, see
444     *  {@link #toString(int, int)}.</p>
445     */
446    @Override
447    public String toString();
448
449    /**
450     *  <p>{@summary Provides a String representation of this value}, in the
451     *  format</p>
452     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
453     *  <p>for the given
454     *  {@link Locale}
455     *  that determines the decimal separator, like &quot;{@code 4.5 m}&quot;
456     *  vs. &quot;{@code 4,5 m}&quot;.</p>
457     *  <p>The precision is applied to the numerical part only. The width
458     *  includes the
459     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
460     *
461     *  @param  locale  The locale to use.
462     *  @param  width   The minimum number of characters to be written to the
463     *      output. If the length of the converted value is less than the width
464     *      then the output will be padded by '&nbsp;' until the total number
465     *      of characters equals width. The padding is at the beginning, as
466     *      numerical values are usually right justified. If {@code width} is
467     *      -1 then there is no minimum.
468     *  @param  precision – The number of digits for the mantissa of the value.
469     *      If {@code precision} is -1 then there is no explicit limit on the
470     *      size of the mantissa.
471     *  @return The String representation for this value.
472     */
473    public default String toString( final Locale locale, final int width, final int precision )
474    {
475        final var retValue = toString( locale, width, precision, false );
476
477        //---* Done *----------------------------------------------------------
478        return retValue;
479    }   //  toString()
480
481    /**
482     *  <p>{@summary Provides a String representation of this value}, in the
483     *  format</p>
484     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
485     *  <p>for the given
486     *  {@link Locale}
487     *  that determines the decimal separator, like &quot;{@code 4.5 m}&quot;
488     *  vs. &quot;{@code 4,5 m}&quot;.</p>
489     *  <p>The precision is applied to the numerical part only. The width
490     *  includes the
491     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
492     *
493     *  @param  locale  The locale to use.
494     *  @param  width   The minimum number of characters to be written to the
495     *      output. If the length of the converted value is less than the width
496     *      then the output will be padded by '&nbsp;' until the total number
497     *      of characters equals width. The padding is at the beginning, as
498     *      numerical values are usually right justified. If {@code width} is
499     *      -1 then there is no minimum.
500     *  @param  precision – The number of digits for the mantissa of the value.
501     *      If {@code precision} is -1 then there is no explicit limit on the
502     *      size of the mantissa.
503     *  @param  useNiceUnit {@true} if the method
504     *      {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()}
505     *      should be used to retrieve the unit symbol, {@false} if the
506     *      usual one is sufficient.
507     *  @return The String representation for this value.
508     */
509    public default String toString( final Locale locale, final int width, final int precision, final boolean useNiceUnit )
510    {
511        final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol();
512        final var effectiveWidth = width - unitSymbol.length() - 1;
513
514        final var format = new StringBuilder( "%" );
515        if( effectiveWidth > 0 ) format.append( effectiveWidth );
516        if( precision >= 0 ) format.append( "." ).append( precision );
517        format.append( "f %s" );
518
519        final var retValue = format( requireNonNullArgument( locale, "locale" ), format.toString(), value(), unitSymbol );
520
521        //---* Done *----------------------------------------------------------
522        return retValue;
523    }   //  toString()
524
525    /**
526     *  <p>{@summary Provides a String representation of this value}, in the
527     *  format</p>
528     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
529     *  <p>and for the
530     *  {@linkplain Locale#getDefault() default Locale},
531     *  like &quot;{@code 4.5 m}&quot;, where the Locale determines the decimal
532     *  separator.</p>
533     *  <p>The precision is applied to the numerical part only. The width
534     *  includes the
535     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
536     *
537     *  @param  width   The minimum number of characters to be written to the
538     *      output. If the length of the converted value is less than the width
539     *      then the output will be padded by '&nbsp;' until the total number
540     *      of characters equals width. The padding is at the beginning, as
541     *      numerical values are usually right justified. If {@code width} is
542     *      -1 then there is no minimum.
543     *  @param  precision – The number of digits for the mantissa of the value.
544     *      If {@code precision} is -1 then there is no explicit limit on the
545     *      size of the mantissa.
546     *  @return The String representation for this value.
547     */
548    public default String toString( final int width, final int precision )
549    {
550        final var retValue = toString( Locale.getDefault(), width, precision );
551
552        //---* Done *----------------------------------------------------------
553        return retValue;
554    }   //  toString()
555
556    /**
557     *  <p>{@summary Provides a String representation of this value}, in the
558     *  format that is defined by the provided format String.</p>
559     *  <p>That format String must contain exactly one '%f' tag and one '%s'
560     *  tag; the first takes the numerical value, the second the unit.</p>
561     *  <p>The provided
562     *  {@link Locale}
563     *  determines the decimal separator and the optional thousands'
564     *  separator.</p>
565     *
566     *  @param  locale  The locale to use.
567     *  @param  format  The format String.
568     *  @param  useNiceUnit {@true} if the method
569     *      {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()}
570     *      should be used to retrieve the unit symbol, {@false} if the
571     *      usual one is sufficient.
572     *  @return The String representation for this value.
573     *  @throws IllegalFormatException  The provided format String is invalid.
574     *
575     *  @see Formatter
576     */
577    public default String toString( final Locale locale, final String format, final boolean useNiceUnit ) throws IllegalFormatException
578    {
579        final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol();
580        final var retValue = format( requireNonNullArgument( locale, "locale" ), requireNotEmptyArgument( format, "format" ), value(), unitSymbol );
581
582        //---* Done *----------------------------------------------------------
583        return retValue;
584    }   //  toString()
585
586    /**
587     *  Returns the numerical value.
588     *
589     *  @return The numerical value, based on current dimension.
590     */
591    public default BigDecimal value() { return convert( getUnit() ); }
592}
593//  interface DimensionedValue
594
595/*
596 *  End of File
597 */