001/*
002 * ============================================================================
003 * Copyright © 2002-2026 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 *
007 * Licensed to the public under the agreements of the GNU Lesser General Public
008 * License, version 3.0 (the "License"). You may obtain a copy of the License at
009 *
010 *      http://www.gnu.org/licenses/lgpl.html
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015 * License for the specific language governing permissions and limitations
016 * under the License.
017 */
018
019package org.tquadrat.foundation.util.stringconverter;
020
021import org.apiguardian.api.API;
022import org.tquadrat.foundation.annotation.ClassVersion;
023import org.tquadrat.foundation.lang.StringConverter;
024import org.tquadrat.foundation.lang.value.Dimension;
025import org.tquadrat.foundation.lang.value.DimensionedValue;
026
027import java.io.Serial;
028import java.math.BigDecimal;
029import java.util.Collection;
030import java.util.List;
031
032import static java.lang.String.format;
033import static java.util.Locale.ROOT;
034import static org.apiguardian.api.API.Status.STABLE;
035import static org.tquadrat.foundation.lang.Objects.isNull;
036import static org.tquadrat.foundation.lang.Objects.nonNull;
037import static org.tquadrat.foundation.util.StringUtils.isEmptyOrBlank;
038
039/**
040 *  <p>{@summary The abstract base class for implementations of
041 *  {@link StringConverter}
042 *  for dimensioned values.}</p>
043 *  <p>The String representations for all dimensioned values have the same
044 *  format: a numeric part followed by the unit for the dimension, separated by
045 *  whitespace (one or more blanks). For example: <code>15&nbsp;m</code> or
046 *  <code>16.0&nbsp;t</code>.</p>
047 *
048 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
049 *  @version $Id: DimensionedValueStringConverter.java 1195 2026-04-15 21:33:40Z tquadrat $
050 *  @since 0.25.3
051 *
052 *  @param  <D> The type for the dimension.
053 *  @param  <V> The type for the dimensioned value.
054 *
055 *  @UMLGraph.link
056 */
057@ClassVersion( sourceVersion = "$Id: DimensionedValueStringConverter.java 1195 2026-04-15 21:33:40Z tquadrat $" )
058@API( status = STABLE, since = "0.25.3" )
059public abstract class DimensionedValueStringConverter<D extends Dimension,V extends DimensionedValue<D>> implements StringConverter<V>
060{
061        /*-----------*\
062    ====** Constants **========================================================
063        \*-----------*/
064    /**
065     *  The error message for an invalid value {@value}.
066     */
067    public static final String MSG_InvalidValue = "'%s' cannot be parsed as a dimensioned value";
068
069        /*------------*\
070    ====** Attributes **=======================================================
071        \*------------*/
072    /**
073     *  The subject class for this converter.
074     *
075     *  @serial
076     */
077    private final Class<V> m_SubjectClass;
078
079        /*------------------------*\
080    ====** Static Initialisations **===========================================
081        \*------------------------*/
082    /**
083     *  The serial version UID for objects of this class: {@value}.
084     *
085     *  @hidden
086     */
087    @Serial
088    private static final long serialVersionUID = 1L;
089
090        /*--------------*\
091    ====** Constructors **=====================================================
092        \*--------------*/
093    /**
094     *  Creates a new instance of {@code DimensionedValueStringConverter}.
095     *
096     *  @param  subjectClass    The subject class.
097     */
098    protected DimensionedValueStringConverter( final Class<V> subjectClass )
099    {
100        m_SubjectClass = subjectClass;
101    }   //  DimensionedValueStringConverter()
102
103        /*---------*\
104    ====** Methods **==========================================================
105        \*---------*/
106    /**
107     *  Creates an instance of
108     *  {@link DimensionedValue}
109     *  from the given arguments.
110     *
111     *  @param  number  The value.
112     *  @param  dimension   The dimension.
113     *  @return The dimensioned value.
114     */
115    protected abstract V createValue( BigDecimal number, D dimension );
116
117    /**
118     *  {@inheritDoc}
119     */
120    @Override
121    public final V fromString( final CharSequence source ) throws IllegalArgumentException
122    {
123        V retValue = null;
124        if( nonNull( source ) )
125        {
126            if( isEmptyOrBlank( source ) ) throw new IllegalArgumentException( format( MSG_InvalidValue, source ) );
127            final var parts = source.toString().split( "\\s" );
128            if( parts.length != 2 ) throw new IllegalArgumentException( format( MSG_InvalidValue, source ) );
129
130            //---* Get the number *--------------------------------------------
131            final var number = BigDecimalStringConverter.INSTANCE.fromString( parts [0] );
132
133            //---* Get the dimension *-----------------------------------------
134            final var dimension = unitFromSymbol( parts [1] );
135
136            //---* Create the return value *-----------------------------------
137            retValue = createValue( number, dimension );
138        }
139
140        //---* Done *----------------------------------------------------------
141        return retValue;
142    }   //  fromString()
143
144    /**
145     *  Provides the subject class for this converter.
146     *
147     * @return The subject class.
148     */
149    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
150    public final Collection<Class<V>> getSubjectClass() { return List.of( m_SubjectClass ); }
151
152    /**
153     *  {@inheritDoc}
154     */
155    @Override
156    public String toString( final V source )
157    {
158        final var retValue = isNull( source ) ? null : source.toString( ROOT,-1, -1 );
159
160        //---* Done *----------------------------------------------------------
161        return retValue;
162    }   //  toString()
163
164    /**
165     *  Returns a String representation of given value.<br>
166     *  <br>The precision is applied to the numerical part only. The width
167     *  includes the
168     *  {@linkplain Dimension#unitSymbol() unit symbol}, too.
169     *
170     *  @param  source  The object to convert; can be {@code null}.
171     *  @param  width   The minimum number of characters to be written to the
172     *      output. If the length of the converted value is less than the width
173     *      then the output will be padded by '&nbsp;' until the total number
174     *      of characters equals width. The padding is at the beginning, as
175     *      numerical values are usually right justified. If {@code width} is
176     *      -1 then there is no minimum.
177     *  @param  precision – The number of digits for the mantissa of the value.
178     *      If {@code precision} is -1 then there is no explicit limit on the
179     *      size of the mantissa.
180     *  @return The String representation for this value.
181     */
182    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
183    public String toString( final V source, final int width, final int precision )
184    {
185        final var retValue = isNull( source ) ? null : source.toString( ROOT, width, precision );
186
187        //---* Done *----------------------------------------------------------
188        return retValue;
189    }   //  toString()
190
191    /**
192     *  Determines the unit instance from the given unit symbol.
193     *
194     *  @param  symbol  The unit symbol.
195     *  @return The unit instance.
196     *  @throws IllegalArgumentException    The given unit symbol is unknown
197     *      for the respective dimension.
198     */
199    protected abstract D unitFromSymbol( String symbol ) throws IllegalArgumentException;
200}
201//  class DimensionedValueStringConverter
202
203/*
204 *  End of File
205 */