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.ap.impl.codebuilders;
019
020import static java.lang.String.format;
021import static java.util.stream.Collectors.joining;
022import static javax.lang.model.element.Modifier.FINAL;
023import static javax.lang.model.element.Modifier.PRIVATE;
024import static javax.lang.model.element.Modifier.PUBLIC;
025import static org.apiguardian.api.API.Status.MAINTAINED;
026import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_DuplicateOptionName;
027import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_InvalidCLIType;
028import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_NoArgumentIndex;
029import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_NoOptionName;
030import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.ELEMENTTYPE_IS_ENUM;
031import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_CLI_MANDATORY;
032import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_CLI_MULTIVALUED;
033import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_ARGUMENT;
034import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_OPTION;
035import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_CLIDefinitions;
036import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_CLIError;
037import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_WriteLock;
038import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardMethod.STD_METHOD_GetRessourceBundle;
039import static org.tquadrat.foundation.javacomposer.Primitives.BOOLEAN;
040import static org.tquadrat.foundation.javacomposer.Primitives.VOID;
041import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.REDUNDANT_EXPLICIT_VARIABLE_TYPE;
042import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.createSuppressWarningsAnnotation;
043import static org.tquadrat.foundation.lang.DebugOutput.ifDebug;
044import static org.tquadrat.foundation.lang.Objects.nonNull;
045import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
046import static org.tquadrat.foundation.util.StringUtils.capitalize;
047
048import java.io.IOException;
049import java.io.OutputStream;
050import java.util.ArrayList;
051import java.util.Collection;
052import java.util.HashMap;
053import java.util.HashSet;
054import java.util.List;
055import java.util.Map;
056import java.util.Optional;
057import java.util.function.BiConsumer;
058
059import org.apiguardian.api.API;
060import org.tquadrat.foundation.annotation.ClassVersion;
061import org.tquadrat.foundation.ap.IllegalAnnotationError;
062import org.tquadrat.foundation.config.CmdLineException;
063import org.tquadrat.foundation.config.ConfigUtil;
064import org.tquadrat.foundation.config.ap.CollectionKind;
065import org.tquadrat.foundation.config.ap.PropertySpec;
066import org.tquadrat.foundation.config.cli.CmdLineValueHandler;
067import org.tquadrat.foundation.config.cli.SimpleCmdLineValueHandler;
068import org.tquadrat.foundation.config.internal.ClassRegistry;
069import org.tquadrat.foundation.config.spi.CLIArgumentDefinition;
070import org.tquadrat.foundation.config.spi.CLIDefinition;
071import org.tquadrat.foundation.config.spi.CLIOptionDefinition;
072import org.tquadrat.foundation.javacomposer.ArrayTypeName;
073import org.tquadrat.foundation.javacomposer.ClassName;
074import org.tquadrat.foundation.javacomposer.FieldSpec;
075import org.tquadrat.foundation.javacomposer.LambdaSpec;
076import org.tquadrat.foundation.javacomposer.ParameterizedTypeName;
077import org.tquadrat.foundation.javacomposer.TypeName;
078import org.tquadrat.foundation.javacomposer.WildcardTypeName;
079import org.tquadrat.foundation.lang.Objects;
080import org.tquadrat.foundation.util.stringconverter.EnumStringConverter;
081
082/**
083 *  The
084 *  {@linkplain org.tquadrat.foundation.config.ap.impl.CodeBuilder code builder implementation}
085 *  for the CLI stuff, as defined in
086 *  {@link org.tquadrat.foundation.config.CLIBeanSpec}.
087 *
088 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
089 *  @version $Id: CLIBeanBuilder.java 1231 2026-05-05 14:28:23Z tquadrat $
090 *  @UMLGraph.link
091 *  @since 0.1.0
092 */
093@SuppressWarnings( "OverlyCoupledClass" )
094@ClassVersion( sourceVersion = "$Id: CLIBeanBuilder.java 1231 2026-05-05 14:28:23Z tquadrat $" )
095@API( status = MAINTAINED, since = "0.1.0" )
096public final class CLIBeanBuilder extends CodeBuilderBase
097{
098        /*------------*\
099    ====** Attributes **=======================================================
100        \*------------*/
101    /**
102     *  The handler classes.
103     */
104    private final Map<TypeName,ClassName> m_HandlerClasses = new HashMap<>();
105
106        /*--------------*\
107    ====** Constructors **=====================================================
108        \*--------------*/
109    /**
110     *  Creates a new instance of {@code CLIBeanBuilder}.
111     *
112     *  @param  context The code generator context.
113     */
114    public CLIBeanBuilder( final CodeGeneratorContext context )
115    {
116        super( context );
117
118        for( final var entry : ClassRegistry.m_HandlerClasses.entrySet() )
119        {
120            m_HandlerClasses.put( TypeName.from( entry.getKey() ), ClassName.from( entry.getValue() ) );
121        }
122    }   //  CLIBeanBuilder()
123
124        /*---------*\
125    ====** Methods **==========================================================
126        \*---------*/
127    /**
128     *  {@inheritDoc}
129     *  <p>This method checks whether there are any properties that are either
130     *  options or arguments, and does the build only when there is at least
131     *  one.</p>
132     *  <p>Not building CLI stuff will let crash the compilation of the
133     *  generated code, but this is intended: either the annotation for the
134     *  CLI properties is missing, or the interface
135     *  {@link org.tquadrat.foundation.config.CLIBeanSpec}
136     *  was added to the configuration bean specification in error.</p>
137     */
138    @Override
139    public final void build()
140    {
141        var doBuild = false;
142        for( final var iterator = getProperties(); iterator.hasNext() && !doBuild; )
143        {
144            final var property = iterator.next();
145            doBuild = property.hasFlag( PROPERTY_IS_OPTION ) || property.hasFlag( PROPERTY_IS_ARGUMENT );
146        }
147        if( doBuild ) doBuild();
148    }   //  build()
149
150    /**
151     *  Composes the code that creates the CLI value handler for the given
152     *  property.
153     *
154     *  @param  property    The property.
155     *  @return The name of the method that creates the CLI value handler for
156     *      this property.
157     */
158    @SuppressWarnings( {"OverlyCoupledMethod", "OverlyComplexMethod"} )
159    private final String composeValueHandlerCreation( final PropertySpec property )
160    {
161        //---* The method name *-----------------------------------------------
162        final var retValue = format( "composeValueHandler_%s", capitalize( property.getPropertyName() ) );
163
164        final var objectType = WildcardTypeName.subtypeOf( Object.class );
165        final var handlerType = ParameterizedTypeName.from( ClassName.from( CmdLineValueHandler.class ), objectType );
166        final var builder = getComposer().codeBlockBuilder();
167
168        final ParameterizedTypeName lambdaType;
169        final LambdaSpec lambda;
170
171        if( property.isCollection() )
172        {
173            if( property.getCollectionKind() == CollectionKind.MAP )
174            {
175                throw new IllegalAnnotationError( "Property '%s' is a map of type '%s'; maps are currently not supported for CLI properties".formatted( property.getPropertyName(), property.getPropertyType().toString() ) );
176            }
177
178            //---* Determine the element type of the collection *--------------
179            final var elementType = property.getElementType()
180                .orElseThrow( () -> new IllegalAnnotationError( "Cannot determine element type for property '%s'".formatted( property.getPropertyName() ) ) );
181
182            //---* The lambda that adds the value to the attribute *-----------
183            lambdaType = ParameterizedTypeName.from( ClassName.from( BiConsumer.class ), ClassName.from( String.class ), elementType );
184            lambda = getComposer().lambdaBuilder()
185                .addParameter( "propertyName" )
186                .addParameter( "value" )
187                .addCode( "$N.add( value )", property.getFieldName() )
188                .build();
189
190            //---* Retrieve the class for the value handler *------------------
191            final var valueHandlerClass = retrieveValueHandlerClass( property );
192            if( valueHandlerClass.isPresent() )
193            {
194                builder.addStatement( "final $T retValue = new $T( lambda ) ", handlerType, valueHandlerClass.get() );
195            }
196            else
197            {
198                //---* Get the StringConverter for the element type *--------------
199                final var stringConverter = property.getStringConverterClass()
200                    .or( () -> getStringConverter( elementType ) )
201                    .or( () -> Optional.ofNullable( property.hasFlag( ELEMENTTYPE_IS_ENUM ) ? ClassName.from( EnumStringConverter.class ) : null ) )
202                    .orElseThrow( () -> new IllegalAnnotationError( "Property '%1$s': cannot find StringConverter for '%2$s'".formatted( property.getPropertyName(), elementType.toString() ) ) );
203
204                switch( determineStringConverterInstantiation( stringConverter, property.hasFlag( ELEMENTTYPE_IS_ENUM ) ) )
205                {
206                    case BY_INSTANCE -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, $3T.INSTANCE )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
207                    case THROUGH_CONSTRUCTOR -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T() )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
208                    case AS_ENUM -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T( $4T.class ) )", handlerType, SimpleCmdLineValueHandler.class, stringConverter, elementType );
209                }
210            }
211        }
212        else
213        {
214            //---* The lambda that sets the value to the attribute *-----------
215            lambdaType = ParameterizedTypeName.from( ClassName.from( BiConsumer.class ), ClassName.from( String.class ), property.getPropertyType().box() );
216            lambda = getComposer().lambdaBuilder()
217                .addParameter( "propertyName" )
218                .addParameter( "value" )
219                .addCode( "$N = value", property.getFieldName() )
220                .build();
221
222            //---* Retrieve the class for the value handler *------------------
223            final var valueHandlerClass = retrieveValueHandlerClass( property );
224            //noinspection OverlyLongLambda
225            valueHandlerClass.ifPresentOrElse(
226                t -> builder.addStatement( "final $T retValue = new $T( lambda ) ", handlerType, t ),
227                () ->
228                {
229                    final var stringConverter = property.getStringConverterClass()
230                        .orElseThrow( () -> new IllegalAnnotationError( "No String converter for property '%s'".formatted( property.getPropertyName() ) ) );
231                    switch( determineStringConverterInstantiation( stringConverter, property.isEnum() ) )
232                    {
233                        case BY_INSTANCE -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, $3T.INSTANCE )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
234                        case THROUGH_CONSTRUCTOR -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T() )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
235                        case AS_ENUM -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T( $4T.class ) )", handlerType, SimpleCmdLineValueHandler.class, stringConverter, property.getPropertyType() );
236                    }
237                });
238        }
239
240        //---* Compose the method *--------------------------------------------
241        final var method = getComposer().methodBuilder( retValue )
242            .addModifiers( PRIVATE, FINAL )
243            .addJavadoc(
244                """
245                Creates the value handler for the property &quot;$L.&quot;.
246                """, property.getPropertyName() )
247            .returns( handlerType, "The value handler." )
248            .addCode(
249                """
250                $L
251                """, createSuppressWarningsAnnotation( getComposer(), REDUNDANT_EXPLICIT_VARIABLE_TYPE ) )
252            .addStatement( "final $T lambda = $L", lambdaType, lambda )
253            .addCode( builder.build() )
254            .addCode( getComposer().createReturnStatement() )
255            .build();
256        addMethod( method );
257
258        //---* Done *----------------------------------------------------------
259        return retValue;
260    }   //  composeValueHandlerCreation()
261
262    /**
263     *  Creates the implementation for the method
264     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#dumpParamFileTemplate(OutputStream)}.
265     */
266    private final void createDumpParamFileTemplate()
267    {
268        final var arg = getComposer().parameterBuilder( OutputStream.class, "outputStream", FINAL )
269            .build();
270        final var method = getComposer().methodBuilder( "dumpParamFileTemplate" )
271            .addModifiers( PUBLIC, FINAL )
272            .addAnnotation( Override.class )
273            .addParameter( arg )
274            .returns( VOID )
275            .addException( IOException.class )
276            .addJavadoc( getComposer().createInheritDocComment() )
277            .addStatement( "$T.dumpParamFileTemplate( $N, $N )", ConfigUtil.class, getField( STD_FIELD_CLIDefinitions ), arg )
278            .build();
279        addMethod( method );
280    }   //  createDumpParamFileTemplate()
281
282    /**
283     *  Creates the implementation for the method
284     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#parseCommandLine(String[])}.
285     *
286     *  @param  registry    The registry of the properties that are exposed for
287     *      the CLI.
288     *  @param  errorMsgHolder  The field for the parse errors.
289     */
290    private final void createParseCommandLine( final FieldSpec registry, final FieldSpec errorMsgHolder )
291    {
292        final TypeName typeName = ArrayTypeName.of( String.class );
293        final var arg = getComposer().parameterBuilder( typeName, "args", FINAL )
294            .build();
295        final var methodBuilder = getComposer().methodBuilder( "parseCommandLine" )
296            .addModifiers( PUBLIC, FINAL )
297            .addAnnotation( Override.class )
298            .addParameter( arg )
299            .returns( BOOLEAN )
300            .addJavadoc( getComposer().createInheritDocComment() )
301            .addStatement( "var retValue = true" );
302        if( isSynchronized() )
303        {
304            methodBuilder.beginControlFlow(
305            """
306                try( final var ignored = $N.lock() )
307                """, getField( STD_FIELD_WriteLock ) );
308        }
309        else
310        {
311            methodBuilder.beginControlFlow(
312            """
313                try
314                """ );
315        }
316        methodBuilder.addStatement( "$T.parseCommandLine( $N, $N )", ConfigUtil.class, registry, arg )
317            .addStatement( "$N = null", errorMsgHolder )
318            .nextControlFlow(
319                """
320
321                catch( final $T e )
322                """, CmdLineException.class )
323            .addStatement( "$N = e.getLocalizedMessage()", errorMsgHolder )
324            .addStatement( "retValue = false" )
325            .endControlFlow()
326            .addCode( getComposer().createReturnStatement() )
327            .build();
328
329        final var method =methodBuilder.build();
330
331        addMethod( method );
332    }   //  createParseCommandLine()
333
334    /**
335     *  Creates the implementation for the method
336     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#printUsage(OutputStream, CharSequence)}.
337     *
338     *  @param  registry    The registry of the properties that are exposed for
339     *      the CLI.
340     */
341    private final void createPrintUsage( final FieldSpec registry )
342    {
343        final var arg0 = getComposer().parameterBuilder( OutputStream.class, "outputStream", FINAL )
344            .build();
345        final var arg1 = getComposer().parameterBuilder( CharSequence.class, "command", FINAL )
346            .build();
347        final var method = getComposer().methodBuilder( "printUsage" )
348            .addModifiers( PUBLIC, FINAL )
349            .addAnnotation( Override.class )
350            .addParameter( arg0 )
351            .addParameter( arg1 )
352            .returns( VOID )
353            .addException( IOException.class )
354            .addJavadoc( getComposer().createInheritDocComment() )
355            .addStatement( "$T.printUsage( $N, $N(), $N, $N )", ConfigUtil.class, arg0, getMethod( STD_METHOD_GetRessourceBundle ), arg1, registry )
356            .build();
357        addMethod( method );
358    }   //  createPrintUsage()
359
360    /**
361     *  Creates the implementation for the method
362     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#retrieveParseErrorMessage()}.
363     *
364     *  @param  errorMsgHolder  The field for the parse errors.
365     */
366    private final void createRetrieveParseErrorMessage( final FieldSpec errorMsgHolder )
367    {
368        final var typeName = ParameterizedTypeName.from( Optional.class, String.class );
369        final var method = getComposer().methodBuilder( "retrieveParseErrorMessage" )
370            .addModifiers( PUBLIC, FINAL )
371            .addAnnotation( Override.class )
372            .returns( typeName )
373            .addJavadoc( getComposer().createInheritDocComment() )
374            .addStatement( "return $T.ofNullable( $N )", Optional.class, errorMsgHolder )
375            .build();
376        addMethod( method );
377    }   //  createRetrieveParseErrorMessage()
378
379    /**
380     *  Is called by
381     *  {@link #build()}
382     *  to do the work – only if there is work to do …
383     */
384    private final void doBuild()
385    {
386        //---* Create the registry for the CLI definitions *-------------------
387        final var registryType = ParameterizedTypeName.from( ClassName.from( List.class ), TypeName.from( CLIDefinition.class ) );
388        final var registry = getComposer().fieldBuilder( registryType, STD_FIELD_CLIDefinitions.toString(), PRIVATE, FINAL )
389            .addJavadoc(
390                """
391                The registry for the CLI definitions
392                """ )
393            .initializer( "new $T<>()", ArrayList.class )
394            .build();
395        addField( STD_FIELD_CLIDefinitions, registry );
396
397        //---* Create the field for the CLI parsing errors *-------------------
398        final var errorMsgHolder = getComposer().fieldBuilder( String.class, STD_FIELD_CLIError.toString(), PRIVATE )
399            .addJavadoc(
400                """
401                The last error message from a call to
402                {@link #parseCommandLine(String[])}.
403
404                @see #retrieveParseErrorMessage()
405                """ )
406            .initializer( "null" )
407            .build();
408        addField( STD_FIELD_CLIError, errorMsgHolder );
409
410        //---* Add the methods from CLIBeanSpec *------------------------------
411        createDumpParamFileTemplate();
412        createParseCommandLine( registry, errorMsgHolder );
413        createPrintUsage( registry );
414        createRetrieveParseErrorMessage( errorMsgHolder );
415
416        /*
417         * The names of previously encountered options, collected to avoid
418         * collisions.
419         */
420        final Collection<String> alreadyUsedOptions = new HashSet<>();
421
422        //---* Add the code to the constructor *-------------------------------
423        final var objectType = WildcardTypeName.subtypeOf( Object.class );
424        final var handlerName = "valueHandler";
425        final var handlerType = ParameterizedTypeName.from( ClassName.from( CmdLineValueHandler.class ), objectType );
426
427        final var definitionName = "cliDefinition";
428
429        final var builder = getComposer().codeBlockBuilder().add(
430                """
431    
432                /*
433                 * Initialise the CLI definitions.
434                 */
435                """ )
436            .addStatement( "$T $L", handlerType, handlerName )
437            .addStatement( "$T $L", CLIDefinition.class, definitionName );
438
439        CLIPropertiesLoop:
440        for( final var iterator = getProperties(); iterator.hasNext(); )
441        {
442            final var property = iterator.next();
443            if( !property.isOnCLI() ) continue CLIPropertiesLoop;
444
445            //---* Create the value handler *----------------------------------
446            builder.add(
447                    """
448                    
449                    /*
450                     * CLI definition for Property &quot;$L&quot;.
451                     */
452                    """, property.getPropertyName()
453                )
454                .addStatement( "$L = $L()", handlerName, composeValueHandlerCreation( property ) );
455
456            //---* Create the CLI definition *---------------------------------
457            final var usage = property.getCLIUsage().orElse( null );
458            final var usageKey = property.getCLIUsageKey().orElse( null );
459            final var metaVar = property.getCLIMetaVar().orElse( null );
460            final var required = Boolean.valueOf( property.hasFlag( PROPERTY_CLI_MANDATORY ) );
461            final var multiValued = Boolean.valueOf( property.hasFlag( PROPERTY_CLI_MULTIVALUED ) );
462            final var format = property.getCLIFormat().orElse( null );
463            if( property.hasFlag( PROPERTY_IS_ARGUMENT ) )
464            {
465                builder.addStatement( "$1L = new $2T( $3S, $4L, $5S, $6S, $7S, $8L, $9L, $10L, $11S )",
466                    definitionName,
467                    CLIArgumentDefinition.class,
468                    property.getPropertyName(),
469                    Integer.valueOf( property.getCLIArgumentIndex().orElseThrow( () -> new IllegalAnnotationError( format( MSG_NoArgumentIndex, property.getPropertyName() ) ) ) ),
470                    usage,
471                    usageKey,
472                    metaVar,
473                    required,
474                    handlerName,
475                    multiValued,
476                    format );
477            }
478            else if( property.hasFlag( PROPERTY_IS_OPTION ) )
479            {
480                final var optionNames = property.getCLIOptionNames().orElseThrow( () -> new IllegalAnnotationError( format( MSG_NoOptionName, property.getPropertyName() ) ) );
481                for( final var optionName : optionNames )
482                {
483                    if( !alreadyUsedOptions.add( optionName ) )
484                    {
485                        throw new IllegalAnnotationError( format( MSG_DuplicateOptionName, optionName, property.getPropertyName() ) );
486                    }
487                }
488                final var names = optionNames.stream()
489                    .map( s -> format( "\"%s\"", s ) )
490                    .collect( joining( ", ", "List.of( ", " )" ) );
491                builder.addStatement( "$1L = new $2T( $3S, $4L, $5S, $6S, $7S, $8L, $9L, $10L, $11S )",
492                    definitionName,
493                    CLIOptionDefinition.class,
494                    property.getPropertyName(),
495                    names,
496                    usage,
497                    usageKey,
498                    metaVar,
499                    required,
500                    handlerName,
501                    multiValued,
502                    format );
503            }
504            else
505            {
506                throw new IllegalAnnotationError( format( MSG_InvalidCLIType, property.getPropertyName() ) );
507            }
508            builder.addStatement( "$N.add( $L )", registry, definitionName );
509        }
510        addConstructorCode( builder.build() );
511    }   //  doBuild()
512
513    /**
514     *  <p>{@summary Retrieves the class for the value handler for the given
515     *  property.} If returning
516     *  {@link Optional#empty() empty},
517     *  an instance of
518     *  {@link SimpleCmdLineValueHandler}
519     *  must be used that will be instantiated with the
520     *  {@link org.tquadrat.foundation.lang.StringConverter}
521     *  retrieved by a call to
522     *  {@link PropertySpec#getStringConverterClass()}.</p>
523     *
524     *  @param  property    The property.
525     *  @return An instance of
526     *      {@link Optional}
527     *      that holds the respective type name.
528     */
529    private final Optional<TypeName> retrieveValueHandlerClass( final PropertySpec property )
530    {
531        var retValue = requireNonNullArgument( property, "property" ).getCLIValueHandlerClass();
532        if( retValue.isEmpty() )
533        {
534            final var handlerClass = m_HandlerClasses.get( property.getPropertyType() );
535            if( nonNull( handlerClass ) )
536            {
537                retValue = Optional.of( handlerClass );
538            }
539        }
540        ifDebug( a ->
541        {
542            final var propertyName = ((PropertySpec) a [0]).getPropertyName();
543            final var propertyType = ((PropertySpec) a [0]).getPropertyType().toString();
544            //noinspection unchecked
545            final var handlerClass = Objects.toString( ((Optional<TypeName>) a [1]).orElse( null ) );
546            return format( "property: %1$s%n\tpropertyType: %2$s%n\thandlerClass: %3$s", propertyName, propertyType, handlerClass );
547        }, property, retValue );
548
549        //---* Done *----------------------------------------------------------
550        return retValue;
551    }   //  retrieveValueHandlerClass()
552}
553//  class CLIBeanBuilder
554
555/*
556 *  End of File
557 */