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