001/*
002 * ============================================================================
003 *  Copyright © 2002-2023 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.config.cli;
019
020import static org.apiguardian.api.API.Status.INTERNAL;
021import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
022
023import java.time.format.DateTimeFormatter;
024import java.time.temporal.Temporal;
025import java.util.Collection;
026import java.util.List;
027import java.util.Optional;
028import java.util.function.BiConsumer;
029
030import org.apiguardian.api.API;
031import org.tquadrat.foundation.annotation.ClassVersion;
032import org.tquadrat.foundation.config.CmdLineException;
033import org.tquadrat.foundation.config.spi.CLIDefinition;
034import org.tquadrat.foundation.config.spi.Parameters;
035import org.tquadrat.foundation.i18n.Message;
036import org.tquadrat.foundation.i18n.Translation;
037import org.tquadrat.foundation.lang.StringConverter;
038import org.tquadrat.foundation.util.stringconverter.TimeDateStringConverter;
039
040/**
041 *  <p>{@summary The abstract base class for implementations of
042 *  {@link CmdLineValueHandler}
043 *  for types that extend
044 *  {@link Temporal}.}</p>
045 *  <p>Except for
046 *  {@link InstantValueHandler InstantValueHandler},
047 *  the format for the date/time data on the command line can be modified by
048 *  setting the
049 *  {@link CLIDefinition#format() format}
050 *  parameter of the
051 *  {@link org.tquadrat.foundation.config.Option &#64;Option}
052 *  or
053 *  {@link org.tquadrat.foundation.config.Argument &#64;Argument}
054 *  annotation. The value for that parameter has to conform the requirements as
055 *  for
056 *  {@link DateTimeFormatter#ofPattern(String)}.</p>
057 *  <p>All implementations do allow the value &quot;now&quot; instead of a
058 *  concrete date/time value; this will be interpreted always as the current
059 *  date and/or time.</p>
060 *
061 *  @param  <T> The type that is handled by this class.
062 *
063 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
064 *  @version $Id: TimeValueHandler.java 1061 2023-09-25 16:32:43Z tquadrat $
065 *  @since 0.0.1
066 *
067 *  @UMLGraph.link
068 *
069 *  @see DateTimeFormatter
070 */
071@ClassVersion( sourceVersion = "$Id: TimeValueHandler.java 1061 2023-09-25 16:32:43Z tquadrat $" )
072@API( status = INTERNAL, since = "0.0.1" )
073public abstract class TimeValueHandler<T extends Temporal> extends CmdLineValueHandler<T>
074{
075        /*-----------*\
076    ====** Constants **========================================================
077        \*-----------*/
078    /**
079     *  The error message about an invalid date/time on the command line:
080     *  {@value}.
081     */
082    public static final String MSG_InvalidDateTimeFormat = TimeDateStringConverter.MSG_InvalidDateTimeFormat;
083
084    /**
085     *  The resource bundle key for the message about an invalid date/time
086     *  String on the command line.
087     */
088    @Message
089    (
090        description = "The error message about an invalid date/time String on the command line.",
091        translations =
092        {
093            @Translation( language = "en", text = TimeDateStringConverter.MSG_InvalidDateTimeFormat ),
094            @Translation( language = "de", text = "'%1$s' ist keine gültige Datums-/Zeitangabe" )
095        }
096    )
097    public static final int MSGKEY_InvalidDateTimeFormat = 30;
098
099    /**
100     *  '{@value}' stands for the current time.
101     */
102    public static final String NOW = "now";
103
104        /*------------*\
105    ====** Attributes **=======================================================
106        \*------------*/
107    /**
108     *  The implementation of
109     *  {@link StringConverter}
110     *  that is used to translate the String value from the command line into
111     *  the desired object instance.
112     */
113    private final TimeDateStringConverter<T> m_StringConverter;
114
115        /*--------------*\
116    ====** Constructors **=====================================================
117        \*--------------*/
118    /**
119     *  Creates a new {@code TimeValueHandler} instance.
120     *
121     *  @param  context The CLI definition that provides the context for this
122     *      value handler.
123     *  @param  valueSetter The
124     *      {@link BiConsumer Consumer}
125     *      that places the translated value to the property.
126     *  @param  stringConverter The implementation of
127     *      {@link StringConverter}
128     *      that is used to translate the String value from the command line
129     *      into the desired object instance in case no formatter is provided.
130     */
131    protected TimeValueHandler( final CLIDefinition context, final BiConsumer<String,T> valueSetter, final TimeDateStringConverter<T> stringConverter )
132    {
133        super( context, valueSetter );
134        m_StringConverter = requireNonNullArgument( stringConverter, "stringConverter" );
135    }   //  TimeValueHandler()
136
137    /**
138     *  Creates a new {@code TimeValueHandler} instance.
139     *
140     *  @param  valueSetter The
141     *      {@link BiConsumer Consumer}
142     *      that places the translated value to the property.
143     *  @param  stringConverter The implementation of
144     *      {@link StringConverter}
145     *      that is used to translate the String value from the command line
146     *      into the desired object instance in case no formatter is provided.
147     */
148    protected TimeValueHandler( final BiConsumer<String,T> valueSetter, final TimeDateStringConverter<T> stringConverter )
149    {
150        super( valueSetter );
151        m_StringConverter = requireNonNullArgument( stringConverter, "stringConverter" );
152    }   //  TimeValueHandler()
153
154        /*---------*\
155    ====** Methods **==========================================================
156        \*---------*/
157    /**
158     *  Creates a non-standard string converter that uses the provided format.
159     *
160     *  @return An instance o
161     *      {@link Optional}
162     *      that holds the
163     *      {@link StringConverter}.
164     */
165    protected abstract Optional<TimeDateStringConverter<T>> createCustomStringConverter();
166
167    /**
168     *  Creates an instance of
169     *  {@link DateTimeFormatter}
170     *  from the provided format.
171     *
172     *  @return An instance of
173     *      {@link Optional}
174     *      that holds the formatter.
175     *
176     *  @see CLIDefinition#format()
177     */
178    protected final Optional<DateTimeFormatter> getFormatter()
179    {
180        final var retValue = getCLIDefinition()
181            .flatMap( CLIDefinition::format )
182            .map( DateTimeFormatter::ofPattern );
183
184        //---* Done *----------------------------------------------------------
185        return retValue;
186    }   //  getFormatter()
187
188    /**
189     *  Get the current time.
190     *
191     *  @return The current time.
192     */
193    protected abstract T getNow();
194
195    /**
196     *  Returns the implementation of
197     *  {@link StringConverter}
198     *  that is used to translate the String value from the command line into
199     *  the desired object instance.
200     *
201     *  @return The string converter.
202     */
203    protected final TimeDateStringConverter<T> getStringConverter() { return m_StringConverter; }
204
205    /**
206     *  Parses the given String to an instance of
207     *  {@link Temporal}.
208     *
209     *  @param  value   The String to parse.
210     *  @return The time/date value.
211     *  @throws IllegalArgumentException  The given value cannot be parsed to a
212     *      {@code Temporal}.
213     */
214    private final T parseDateTime( final CharSequence value ) throws IllegalArgumentException
215    {
216        final var stringConverter = createCustomStringConverter().orElse( getStringConverter() );
217        final var retValue = stringConverter.fromString( value );
218
219        //---* Done *----------------------------------------------------------
220        return retValue;
221    }   //  parseDateTime()
222
223    /**
224     *  {@inheritDoc}
225     */
226    @Override
227    protected final Collection<T> translate( final Parameters params ) throws CmdLineException
228    {
229        Collection<T> retValue = List.of();
230        final var value = requireNonNullArgument( params, "params" ).getParameter( 0 );
231        if( value.equalsIgnoreCase( NOW ) )
232        {
233            retValue = List.of( getNow() );
234        }
235        else
236        {
237            try
238            {
239                retValue = List.of( parseDateTime( value ) );
240            }
241            catch( final IllegalArgumentException e )
242            {
243                throw new CmdLineException( MSG_InvalidDateTimeFormat, e, MSGKEY_InvalidDateTimeFormat, value );
244            }
245        }
246
247        //---* Done *----------------------------------------------------------
248        return retValue;
249    }   //  translate()
250}
251//  class TimeValueHandler
252
253/*
254 *  End of File
255 */