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.util.Locale.ROOT;
022import static org.apiguardian.api.API.Status.STABLE;
023import static org.tquadrat.foundation.lang.Objects.hash;
024import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
025import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
026
027import java.io.Serial;
028import java.math.BigDecimal;
029import java.util.function.BiPredicate;
030
031import org.apiguardian.api.API;
032import org.tquadrat.foundation.annotation.ClassVersion;
033import org.tquadrat.foundation.exception.UnlikelyExceptionError;
034
035/**
036 *  <p>{@summary An abstract base implementation for the interface
037 *  {@link DimensionedValue}
038 *  that is intended as the base for concrete implementations of value
039 *  types.}</p>
040 *  <p>The {@code validator} argument of the constructors is an instance of
041 *  {@link BiPredicate}
042 *  that takes the
043 *  {@linkplain Dimension unit}
044 *  with that the instance is initialised, and the value, converted to the base
045 *  unit. It returns {@true} if the given combination is valid, otherwise
046 *  it returns {@false}.</p>
047 *
048 *  @author Thomas Thrien - thomas.thrien@tquadrat.org
049 *  @version $Id: ValueBase.java 1295 2026-10-18 12:34:27Z tquadrat $
050 *  @since 0.25.1
051 *
052 *  @param  <D> The dimension.
053 *  @param  <I> The implementing type.
054 *
055 *  @UMLGraph.link
056 */
057@SuppressWarnings( "SerializableDeserializableClassInSecureContext" )
058@ClassVersion( sourceVersion = "$Id: ValueBase.java 1295 2026-10-18 12:34:27Z tquadrat $" )
059@API( status = STABLE, since = "0.25.1" )
060public abstract non-sealed class ValueBase<D extends Dimension, I extends DimensionedValue<D>> implements DimensionedValue<D>
061{
062        /*-----------*\
063    ====** Constants **========================================================
064        \*-----------*/
065    /**
066     *  <p>{@summary The default validator.}</p>
067     */
068    @SuppressWarnings( "rawtypes" )
069    protected static final BiPredicate DEFAULT_VALIDATOR = ( _, _ ) -> true;
070
071        /*------------*\
072    ====** Attributes **=======================================================
073        \*------------*/
074    /**
075     *  <p>{@summary The unit for the value.}</p>
076     *
077     *  @serial
078     */
079    private D m_Unit;
080
081    /**
082     *  <p>{@summary The validation function.}</p>
083     *
084     *  @serial
085     */
086    private final BiPredicate<D,BigDecimal> m_Validator;
087
088    /**
089     *  <p>{@summary The numerical value for this instance.}</p>
090     *
091     *  @serial
092     */
093    private final BigDecimal m_Value;
094
095        /*------------------------*\
096    ====** Static Initialisations **===========================================
097        \*------------------------*/
098    /**
099     *  <p>{@summary The serial version UID for objects of this class:
100     *  {@value}.}</p>
101     *
102     *  @hidden
103     */
104    @Serial
105    private static final long serialVersionUID = -2075489505691464486L;
106
107        /*--------------*\
108    ====** Constructors **=====================================================
109        \*--------------*/
110    /**
111     *  <p>{@summary {@constructor}}</p>
112     *
113     *  @param  unit   The unit.
114     *  @param  value   The value; only absolute (positive) values are allowed,
115     *      a sign will be stripped.
116     *  @param  validator   The validator function; can be {@null}.
117     */
118    protected ValueBase( final D unit, final BigDecimal value, final BiPredicate<D,BigDecimal> validator )
119    {
120        requireNonNullArgument( value, "value" );
121        setUnit( unit );
122        final var conversion = unit.toBase();
123        m_Validator = requireNonNullArgument( validator, "validator" );
124        m_Value = validate( conversion.apply( value ) ).abs().stripTrailingZeros();
125    }   //  ValueBase()
126
127    /**
128     *  Creates a new {@code ValueBase} instance.
129     *
130     *  @param  unit   The unit.
131     *  @param  value   The value; it must be possible to parse the given
132     *      String into a
133     *      {@link BigDecimal}.
134     *  @param  validator   The validator function.
135     *  @throws NumberFormatException   The provided value cannot be converted
136     *      into a {@code BigDecimal}.
137     */
138    protected ValueBase( final D unit, final String value, final BiPredicate<D,BigDecimal> validator ) throws NumberFormatException
139    {
140        this( unit, new BigDecimal( requireNotEmptyArgument( value, "value" ) ), validator );
141    }   //  ValueBase()
142
143    /**
144     *  Creates a new {@code ValueBase} instance.
145     *
146     *  @param  <N> The type of {@code value}.
147     *  @param  unit   The unit.
148     *  @param  value   The value.
149     *  @param  validator   The validator function.
150     */
151    protected <N extends Number> ValueBase( final D unit, final N value, final BiPredicate<D,BigDecimal> validator )
152    {
153        this( unit, new BigDecimal( requireNonNullArgument( value, "value" ).toString() ), validator );
154    }   //  ValueBase()
155
156        /*---------*\
157    ====** Methods **==========================================================
158        \*---------*/
159    /**
160     *  {@inheritDoc}
161     */
162    @Override
163    public final BigDecimal baseValue() { return m_Value; }
164
165    /**
166     *  {@inheritDoc}
167     */
168    @Override
169    public I clone()
170    {
171        final I retValue;
172        try
173        {
174            @SuppressWarnings( "unchecked" )
175            final var clone = (I) super.clone();
176            retValue = clone;
177        }
178        catch( final CloneNotSupportedException e )
179        {
180            throw new UnlikelyExceptionError( e );
181        }
182
183        //---* Done *----------------------------------------------------------
184        return retValue;
185    }   //  clone()
186
187    /**
188     *  {@inheritDoc}
189     */
190    @Override
191    public final I copy()
192    {
193        final var retValue = clone();
194
195        //---* Done *----------------------------------------------------------
196        return retValue;
197    }   //  copy()
198
199    /**
200     *  {@inheritDoc}
201     */
202    @Override
203    public final I copy( final D unit )
204    {
205        final var retValue = copy();
206        retValue.setUnit( unit ); // Does the null check ...
207
208        //---* Done *----------------------------------------------------------
209        return retValue;
210    }   //  copy()
211
212    /**
213     *  {@inheritDoc}
214     */
215    @Override
216    public final boolean equals( final Object obj )
217    {
218        var retValue = this == obj;
219        if( !retValue && (obj instanceof final ValueBase<?,?> other ) )
220        {
221            if( baseUnit() == other.baseUnit() )
222            {
223                retValue = baseValue().compareTo( other.baseValue() ) == 0;
224            }
225        }
226
227        //---* Done *----------------------------------------------------------
228        return retValue;
229    }   //  equals()
230
231    /**
232     *  {@inheritDoc}
233     */
234    @Override
235    public final D getUnit() { return m_Unit; }
236
237    /**
238     *  {@inheritDoc}
239     */
240    @Override
241    public final int hashCode() { return hash( baseValue(), baseUnit() ); }
242
243    /**
244     *  {@inheritDoc}
245     */
246    @Override
247    public final void setUnit( final D dimension ) { m_Unit = requireNonNullArgument( dimension, "dimension" ); }
248
249    /**
250     *  {@inheritDoc}
251     */
252    @SuppressWarnings( "CallToNumericToString" )
253    @Override
254    public final String toString() { return format( ROOT, "%s %s", convert( m_Unit ).toString(), m_Unit.unitSymbol() ); }
255
256    /**
257     *  <p>{@summary Validates the given value, based on the type of the
258     *  dimension.}</p>
259     *  <p>This method will be called by the constructors always with the
260     *  {@linkplain #baseUnit() base unit}
261     *  value just before the value will be initialised.</p>
262     *
263     *  @param  value   The value in base units.
264     *  @return The value.
265     *  @throws IllegalArgumentException    The validation failed.
266     */
267    private final BigDecimal validate( final BigDecimal value ) throws IllegalArgumentException
268    {
269        if( !m_Validator.test( m_Unit, requireNonNullArgument( value, "value" ) ) )
270        {
271            //noinspection CallToNumericToString
272            throw new IllegalArgumentException( "Value %s is invalid".formatted( value.toString() ) );
273        }
274
275        //---* Done *----------------------------------------------------------
276        return value;
277    }   //  validate()
278}
279//  class ValueBase
280
281/*
282 *  End of File
283 */