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.UnlikelyExceptionError;
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 *  @author Thomas Thrien - thomas.thrien@tquadrat.org
061 *  @version $Id: DimensionedValue.java 1295 2026-10-18 12:34:27Z 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 1295 2026-10-18 12:34:27Z 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     *  <p>{@summary The
079     *  {@link MathContext}
080     *  that is used for all the operations with dimensioned values.}</p>
081     */
082    public static final MathContext MATH_CONTEXT = new MathContext( 128, HALF_EVEN );
083
084        /*---------*\
085    ====** Methods **==========================================================
086        \*---------*/
087    /**
088     *  <p>{@summary Returns the base unit of the dimension for the value.}</p>
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     *  <p>{@summary Creates a new copy of this value.}</p>
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     *  <p>{@summary Converts this value to the given unit and returns the
136     *  numerical value.}</p>
137     *
138     *  @param  unit    The unit.
139     *  @return The numerical value of this instance, based on the provided
140     *      unit.
141     *
142     *  @see #baseValue()
143     */
144    public default BigDecimal convert( final D unit )
145    {
146        final var conversion = requireNonNullArgument( unit, "unit" ).fromBase();
147        final var retValue = conversion.apply( baseValue() ).stripTrailingZeros();
148
149        //---* Done *----------------------------------------------------------
150        return retValue;
151    }   //  convert()
152
153    /**
154     *  <p>{@summary Creates a new copy of this value.}</p>
155     *
156     *  @return The copy.
157     *
158     *  @see Object#clone()
159     */
160    public DimensionedValue<D> copy();
161
162    /**
163     *  <p>{@summary Creates a new copy of this value.}</p>
164     *
165     *  @param  unit    The unit for the new copy.
166     *  @return The copy.
167     *
168     *  @see Object#clone()
169     */
170    public DimensionedValue<D> copy( final D unit );
171
172    /**
173     *  <p>{@summary Divides the value by a dimension-less value and returns
174     *  the result without changing this instance.}</p>
175     *
176     *  @param  divisor The divisor.
177     *  @return The new value.
178     */
179    public default DimensionedValue<D> divide( final BigDecimal divisor )
180    {
181        final var retValue = newInstance( baseUnit(), baseValue().divide( requireNonNullArgument( divisor, "divisor" ), MATH_CONTEXT ) );
182        retValue.setUnit( getUnit() );
183
184        //---* Done *----------------------------------------------------------
185        return retValue;
186    }   //  divide()
187
188    /**
189     *  <p>{@summary Divides the value by a dimension-less value and returns
190     *  the result without changing this instance.}</p>
191     *
192     *  @param  divisor The divisor.
193     *  @return The new value.
194     */
195    public default DimensionedValue<D> divide( final Number divisor )
196    {
197        final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ).toString() ) );
198        retValue.setUnit( getUnit() );
199
200        //---* Done *----------------------------------------------------------
201        return retValue;
202    }   //  divide()
203
204    /**
205     *  <p>{@summary Divides the value by a dimension-less value and returns
206     *  the result without changing this instance.}</p>
207     *
208     *  @param  divisor The divisor; it must be possible to parse the given
209     *      String into a
210     *      {@link BigDecimal}.
211     *  @return The new value.
212     *  @throws NumberFormatException   The provided value cannot be converted
213     *      into a {@code BigDecimal}.
214     *
215     *  @see BigDecimal#BigDecimal(String)
216     */
217    public default DimensionedValue<D> divide( final String divisor )
218    {
219        final var retValue = divide( new BigDecimal( requireNonNullArgument( divisor, "divisor" ) ) );
220
221        //---* Done *----------------------------------------------------------
222        return retValue;
223    }   //  divide()
224
225    /**
226     *  {@inheritDoc}
227     *  <p>Two instances of a class implementing this interface are equals if
228     *  they are of the <i>same</i> class and if their values, converted to the
229     *  base dimension, are equals.</p>
230     *
231     *  @param  o   The other value.
232     *  @return {@true} if they are equal, {@false} if not.
233     *
234     *  @see Dimension#baseUnit()
235     */
236    @Override
237    public boolean equals( final Object o );
238
239    /**
240     *  {@inheritDoc}
241     *  <p>The precision is applied to the numerical part only. The width
242     *  includes the
243     *  {@linkplain Dimension#unitSymbol() unit symbol},
244     *  too.</p>
245     *
246     *  @note In case the {@code formatter} argument is {@null}, this
247     *      method throws a {@code NullPointerException} and <i>not</i> the
248     *      usual {@code NullArgumentException}, because this method is usually
249     *      called by instances of {@code java.util.Formatter}, and those do
250     *      not know about our special exceptions.
251     *
252     *  @throws NullPointerException    The {@code formatter} argument is
253     *      {@null}.
254     *
255     *  @see Formatter
256     */
257    @SuppressWarnings( "ProhibitedExceptionThrown" )
258    @Override
259    public default void formatTo( final Formatter formatter, final int flags, final int width, final int precision )
260    {
261        if( isNull( formatter ) ) throw new NullPointerException( "formatter is null" );
262
263        var s = toString( width, precision ).trim();
264        final var len = s.length();
265
266        if( ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) && (width > len) )
267        {
268            s = s.concat( " ".repeat( width - len ) );
269        }
270
271        /*
272         * We do not use Formatter.out().append() because we do not know how to
273         * handle the IOException that could be thrown from
274         * Appendable.append(). Using Formatter.format() assumes that Formatter
275         * knows ...
276         */
277        formatter.format( "%s", s );
278    }   //  formatTo()
279
280    /**
281     *  <p>{@summary Returns the unit for the value.}</p>
282     *
283     *  @return The unit.
284     */
285    public D getUnit();
286
287    /**
288     *  {@inheritDoc}
289     *  <p>The hash code is based on the
290     *  {@linkplain #baseValue() base value}
291     *  and
292     *  {@linkplain #baseUnit() base unit}
293     *  only.</p>
294     */
295    @Override
296    public int hashCode();
297
298    /**
299     *  <p>{@summary Multiplies the value by a dimension-less value and returns
300     *  the result without changing this instance.}</p>
301     *
302     *  @param  multiplicand The multiplier.
303     *  @return The new value.
304     */
305    public default DimensionedValue<D> multiply( final BigDecimal multiplicand )
306    {
307        final var retValue = newInstance( baseUnit(), baseValue().multiply( requireNonNullArgument( multiplicand, "multiplicand" ) ) );
308        retValue.setUnit( getUnit() );
309
310        //---* Done *----------------------------------------------------------
311        return retValue;
312    }   //  multiply()
313
314    /**
315     *  <p>{@summary Multiplies the value by a dimension-less value and returns
316     *  the result without changing this instance.}</p>
317     *
318     *  @param  multiplicand The multiplier.
319     *  @return The new value.
320     */
321    public default DimensionedValue<D> multiply( final Number multiplicand )
322    {
323        final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ).toString() ) );
324        retValue.setUnit( getUnit() );
325
326        //---* Done *----------------------------------------------------------
327        return retValue;
328    }   //  multiply()
329
330    /**
331     *  <p>{@summary Multiplies the value by a dimension-less value and returns the result
332     *  without changing this instance.}</p>
333     *
334     *  @param  multiplicand The multiplier; it must be possible to parse the
335     *      given String into a
336     *      {@link BigDecimal}.
337     *  @return The new value.
338     *  @throws NumberFormatException   The provided value cannot be converted
339     *      into a {@code BigDecimal}.
340     *
341     *  @see BigDecimal#BigDecimal(String)
342     */
343    public default DimensionedValue<D> multiply( final String multiplicand )
344    {
345        final var retValue = multiply( new BigDecimal( requireNonNullArgument( multiplicand, "multiplicand" ) ) );
346
347        //---* Done *----------------------------------------------------------
348        return retValue;
349    }   //  multiply()
350
351    /**
352     *  <p>{@summary Creates an instance for the class.}</p>
353     *
354     *  @param  dimension   The dimension for the new instance.
355     *  @param  value   The value for the new instance.
356     *  @return The new instance.
357     */
358    public default DimensionedValue<D> newInstance( final D dimension, final BigDecimal value )
359    {
360        final DimensionedValue<D> retValue;
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 UnlikelyExceptionError( e );
383        }
384
385        //---* Done *----------------------------------------------------------
386        return retValue;
387    }   //  newInstance()
388
389    /**
390     *  <p>{@summary Applies another unit for the value.} This does not affect
391     *  the results of
392     *  {@link #equals(Object)},
393     *  {@link #hashCode()}}
394     *  and
395     *  {@link #compareTo(DimensionedValue)},
396     *  nor that of
397     *  {@link #baseValue()}.</p>
398     *
399     *  @param  unit    The new unit.
400     */
401    public void setUnit( D unit );
402
403    /**
404     *  <p>{@summary Creates a new instance with the sum of this and the given
405     *  value, and returns that.}</p>
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     *  <p>{@summary Creates a new instance with the sum of this and the given
422     *  value, and returns that. The unit for the new instance is that of this
423     *  instance.}</p>
424     *
425     *  @param  summand The value to add.
426     *  @return The instance with the sum.
427     */
428    public default DimensionedValue<D> sum( final DimensionedValue<D> summand )
429    {
430        final var newValue = baseValue().add( requireNonNullArgument( summand, "summand" ).baseValue(), MATH_CONTEXT );
431        final var retValue = newInstance( baseUnit(), newValue );
432
433        //---* Done *----------------------------------------------------------
434        return retValue;
435    }   // sum()
436
437    /**
438     *  <p>{@summary Returns the String representation for this value};
439     *  usually, this is in the format</p>
440     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
441     *  <p>like &quot;{@code 4.5 m}&quot;.</p>
442     *  <p>The precision for the mantissa is
443     *  provided by the
444     *  {@linkplain Dimension#getPrecision() unit}.</p>
445     *  <p>If more control over the output format is required, see
446     *  {@link #toString(int, int)}.</p>
447     */
448    @Override
449    public String toString();
450
451    /**
452     *  <p>{@summary Provides a String representation of this value}, in the
453     *  format</p>
454     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
455     *  <p>for the given
456     *  {@link Locale}
457     *  that determines the decimal separator, like &quot;{@code 4.5 m}&quot;
458     *  vs. &quot;{@code 4,5 m}&quot;.</p>
459     *  <p>The precision is applied to the numerical part only. The width
460     *  includes the
461     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
462     *
463     *  @param  locale  The locale to use.
464     *  @param  width   The minimum number of characters to be written to the
465     *      output. If the length of the converted value is less than the width
466     *      then the output will be padded by '&nbsp;' until the total number
467     *      of characters equals width. The padding is at the beginning, as
468     *      numerical values are usually right justified. If {@code width} is
469     *      -1 then there is no minimum.
470     *  @param  precision – The number of digits for the mantissa of the value.
471     *      If {@code precision} is -1 then there is no explicit limit on the
472     *      size of the mantissa.
473     *  @return The String representation for this value.
474     */
475    public default String toString( final Locale locale, final int width, final int precision )
476    {
477        final var retValue = toString( locale, width, precision, false );
478
479        //---* Done *----------------------------------------------------------
480        return retValue;
481    }   //  toString()
482
483    /**
484     *  <p>{@summary Provides a String representation of this value}, in the
485     *  format</p>
486     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
487     *  <p>for the given
488     *  {@link Locale}
489     *  that determines the decimal separator, like &quot;{@code 4.5 m}&quot;
490     *  vs. &quot;{@code 4,5 m}&quot;.</p>
491     *  <p>The precision is applied to the numerical part only. The width
492     *  includes the
493     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
494     *
495     *  @param  locale  The locale to use.
496     *  @param  width   The minimum number of characters to be written to the
497     *      output. If the length of the converted value is less than the width
498     *      then the output will be padded by '&nbsp;' until the total number
499     *      of characters equals width. The padding is at the beginning, as
500     *      numerical values are usually right justified. If {@code width} is
501     *      -1 then there is no minimum.
502     *  @param  precision – The number of digits for the mantissa of the value.
503     *      If {@code precision} is -1 then there is no explicit limit on the
504     *      size of the mantissa.
505     *  @param  useNiceUnit {@true} if the method
506     *      {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()}
507     *      should be used to retrieve the unit symbol, {@false} if the
508     *      usual one is sufficient.
509     *  @return The String representation for this value.
510     */
511    public default String toString( final Locale locale, final int width, final int precision, final boolean useNiceUnit )
512    {
513        final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol();
514        final var effectiveWidth = width - unitSymbol.length() - 1;
515
516        final var format = new StringBuilder( "%" );
517        if( effectiveWidth > 0 ) format.append( effectiveWidth );
518        if( precision >= 0 ) format.append( "." ).append( precision );
519        format.append( "f %s" );
520
521        final var retValue = format( requireNonNullArgument( locale, "locale" ), format.toString(), value(), unitSymbol );
522
523        //---* Done *----------------------------------------------------------
524        return retValue;
525    }   //  toString()
526
527    /**
528     *  <p>{@summary Provides a String representation of this value}, in the
529     *  format</p>
530     *  <pre><code>&lt;<i>numerical value</i>&gt;&nbsp;&lt;<i>unit symbol</i>&gt;</code></pre>
531     *  <p>and for the
532     *  {@linkplain Locale#getDefault() default Locale},
533     *  like &quot;{@code 4.5 m}&quot;, where the Locale determines the decimal
534     *  separator.</p>
535     *  <p>The precision is applied to the numerical part only. The width
536     *  includes the
537     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.</p>
538     *
539     *  @param  width   The minimum number of characters to be written to the
540     *      output. If the length of the converted value is less than the width
541     *      then the output will be padded by '&nbsp;' until the total number
542     *      of characters equals width. The padding is at the beginning, as
543     *      numerical values are usually right justified. If {@code width} is
544     *      -1 then there is no minimum.
545     *  @param  precision – The number of digits for the mantissa of the value.
546     *      If {@code precision} is -1 then there is no explicit limit on the
547     *      size of the mantissa.
548     *  @return The String representation for this value.
549     */
550    public default String toString( final int width, final int precision )
551    {
552        final var retValue = toString( Locale.getDefault(), width, precision );
553
554        //---* Done *----------------------------------------------------------
555        return retValue;
556    }   //  toString()
557
558    /**
559     *  <p>{@summary Provides a String representation of this value}, in the
560     *  format that is defined by the provided format String.</p>
561     *  <p>That format String must contain exactly one '%f' tag and one '%s'
562     *  tag; the first takes the numerical value, the second the unit.</p>
563     *  <p>The provided
564     *  {@link Locale}
565     *  determines the decimal separator and the optional thousands'
566     *  separator.</p>
567     *
568     *  @param  locale  The locale to use.
569     *  @param  format  The format String.
570     *  @param  useNiceUnit {@true} if the method
571     *      {@link Dimension#unitSymbolForPrinting() unitSymbolForPrinting()}
572     *      should be used to retrieve the unit symbol, {@false} if the
573     *      usual one is sufficient.
574     *  @return The String representation for this value.
575     *  @throws IllegalFormatException  The provided format String is invalid.
576     *
577     *  @see Formatter
578     */
579    public default String toString( final Locale locale, final String format, final boolean useNiceUnit ) throws IllegalFormatException
580    {
581        final var unitSymbol = useNiceUnit ? getUnit().unitSymbolForPrinting() : getUnit().unitSymbol();
582        final var retValue = format( requireNonNullArgument( locale, "locale" ), requireNotEmptyArgument( format, "format" ), value(), unitSymbol );
583
584        //---* Done *----------------------------------------------------------
585        return retValue;
586    }   //  toString()
587
588    /**
589     *  <p>{@summary Returns the numerical value.}</p>
590     *
591     *  @return The numerical value, based on current dimension.
592     */
593    public default BigDecimal value() { return convert( getUnit() ); }
594}
595//  interface DimensionedValue
596
597/*
598 *  End of File
599 */