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