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 1061 2023-09-25 16:32:43Z tquadrat $
090 *  @UMLGraph.link
091 *  @since 0.1.0
092 */
093@SuppressWarnings( "OverlyCoupledClass" )
094@ClassVersion( sourceVersion = "$Id: CLIBeanBuilder.java 1061 2023-09-25 16:32:43Z 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" )
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            //---* Get the StringConverter for the element type *--------------
191            final var stringConverter = property.getStringConverterClass()
192                .or( () -> getStringConverter( elementType ) )
193                .or( () -> Optional.ofNullable( property.hasFlag( ELEMENTTYPE_IS_ENUM ) ? ClassName.from( EnumStringConverter.class ) : null ) )
194                .orElseThrow( () -> new IllegalAnnotationError( "Property '%1$s': cannot find StringConverter for '%2$s'".formatted( property.getPropertyName(), elementType.toString() ) ) );
195
196            switch( determineStringConverterInstantiation( stringConverter, property.hasFlag( ELEMENTTYPE_IS_ENUM ) ) )
197            {
198                case BY_INSTANCE -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, $3T.INSTANCE )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
199                case THROUGH_CONSTRUCTOR -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T() )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
200                case AS_ENUM -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T( $4T.class ) )", handlerType, SimpleCmdLineValueHandler.class, stringConverter, elementType );
201            }
202        }
203        else
204        {
205            //---* The lambda that sets the value to the attribute *-----------
206            lambdaType = ParameterizedTypeName.from( ClassName.from( BiConsumer.class ), ClassName.from( String.class ), property.getPropertyType().box() );
207            lambda = getComposer().lambdaBuilder()
208                .addParameter( "propertyName" )
209                .addParameter( "value" )
210                .addCode( "$N = value", property.getFieldName() )
211                .build();
212
213            //---* Retrieve the class for the value handler *------------------
214            final var valueHandlerClass = retrieveValueHandlerClass( property );
215            //noinspection OverlyLongLambda
216            valueHandlerClass.ifPresentOrElse(
217                t -> builder.addStatement( "final $T retValue = new $T( lambda ) ", handlerType, t ),
218                () ->
219                {
220                    final var stringConverter = property.getStringConverterClass()
221                        .orElseThrow( () -> new IllegalAnnotationError( "No String converter for property '%s'".formatted( property.getPropertyName() ) ) );
222                    switch( determineStringConverterInstantiation( stringConverter, property.isEnum() ) )
223                    {
224                        case BY_INSTANCE -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, $3T.INSTANCE )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
225                        case THROUGH_CONSTRUCTOR -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T() )", handlerType, SimpleCmdLineValueHandler.class, stringConverter );
226                        case AS_ENUM -> builder.addStatement( "final $1T retValue = new $2T<>( lambda, new $3T( $4T.class ) )", handlerType, SimpleCmdLineValueHandler.class, stringConverter, property.getPropertyType() );
227                    }
228                });
229        }
230
231        //---* Compose the method *--------------------------------------------
232        final var method = getComposer().methodBuilder( retValue )
233            .addModifiers( PRIVATE, FINAL )
234            .addJavadoc(
235                """
236                Creates the value handler for the property &quot;$L.&quot;.
237                """, property.getPropertyName() )
238            .returns( handlerType, "The value handler." )
239            .addCode(
240                """
241                $L
242                """, createSuppressWarningsAnnotation( getComposer(), REDUNDANT_EXPLICIT_VARIABLE_TYPE ) )
243            .addStatement( "final $T lambda = $L", lambdaType, lambda )
244            .addCode( builder.build() )
245            .addCode( getComposer().createReturnStatement() )
246            .build();
247        addMethod( method );
248
249        //---* Done *----------------------------------------------------------
250        return retValue;
251    }   //  composeValueHandlerCreation()
252
253    /**
254     *  Creates the implementation for the method
255     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#dumpParamFileTemplate(OutputStream)}.
256     */
257    private final void createDumpParamFileTemplate()
258    {
259        final var arg = getComposer().parameterBuilder( OutputStream.class, "outputStream", FINAL )
260            .build();
261        final var method = getComposer().methodBuilder( "dumpParamFileTemplate" )
262            .addModifiers( PUBLIC, FINAL )
263            .addAnnotation( Override.class )
264            .addParameter( arg )
265            .returns( VOID )
266            .addException( IOException.class )
267            .addJavadoc( getComposer().createInheritDocComment() )
268            .addStatement( "$T.dumpParamFileTemplate( $N, $N )", ConfigUtil.class, getField( STD_FIELD_CLIDefinitions ), arg )
269            .build();
270        addMethod( method );
271    }   //  createDumpParamFileTemplate()
272
273    /**
274     *  Creates the implementation for the method
275     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#parseCommandLine(String[])}.
276     *
277     *  @param  registry    The registry of the properties that are exposed for
278     *      the CLI.
279     *  @param  errorMsgHolder  The field for the parse errors.
280     */
281    private final void createParseCommandLine( final FieldSpec registry, final FieldSpec errorMsgHolder )
282    {
283        final TypeName typeName = ArrayTypeName.of( String.class );
284        final var arg = getComposer().parameterBuilder( typeName, "args", FINAL )
285            .build();
286        final var methodBuilder = getComposer().methodBuilder( "parseCommandLine" )
287            .addModifiers( PUBLIC, FINAL )
288            .addAnnotation( Override.class )
289            .addParameter( arg )
290            .returns( BOOLEAN )
291            .addJavadoc( getComposer().createInheritDocComment() )
292            .addStatement( "var retValue = true" );
293        if( isSynchronized() )
294        {
295            methodBuilder.beginControlFlow(
296            """
297                try( final var ignored = $N.lock() )
298                """, getField( STD_FIELD_WriteLock ) );
299        }
300        else
301        {
302            methodBuilder.beginControlFlow(
303            """
304                try
305                """ );
306        }
307        methodBuilder.addStatement( "$T.parseCommandLine( $N, $N )", ConfigUtil.class, registry, arg )
308            .addStatement( "$N = null", errorMsgHolder )
309            .nextControlFlow(
310                """
311
312                catch( final $T e )
313                """, CmdLineException.class )
314            .addStatement( "$N = e.getLocalizedMessage()", errorMsgHolder )
315            .addStatement( "retValue = false" )
316            .endControlFlow()
317            .addCode( getComposer().createReturnStatement() )
318            .build();
319
320        final var method =methodBuilder.build();
321
322        addMethod( method );
323    }   //  createParseCommandLine()
324
325    /**
326     *  Creates the implementation for the method
327     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#printUsage(OutputStream, CharSequence)}.
328     *
329     *  @param  registry    The registry of the properties that are exposed for
330     *      the CLI.
331     */
332    private final void createPrintUsage( final FieldSpec registry )
333    {
334        final var arg0 = getComposer().parameterBuilder( OutputStream.class, "outputStream", FINAL )
335            .build();
336        final var arg1 = getComposer().parameterBuilder( CharSequence.class, "command", FINAL )
337            .build();
338        final var method = getComposer().methodBuilder( "printUsage" )
339            .addModifiers( PUBLIC, FINAL )
340            .addAnnotation( Override.class )
341            .addParameter( arg0 )
342            .addParameter( arg1 )
343            .returns( VOID )
344            .addException( IOException.class )
345            .addJavadoc( getComposer().createInheritDocComment() )
346            .addStatement( "$T.printUsage( $N, $N(), $N, $N )", ConfigUtil.class, arg0, getMethod( STD_METHOD_GetRessourceBundle ), arg1, registry )
347            .build();
348        addMethod( method );
349    }   //  createPrintUsage()
350
351    /**
352     *  Creates the implementation for the method
353     *  {@link org.tquadrat.foundation.config.CLIBeanSpec#retrieveParseErrorMessage()}.
354     *
355     *  @param  errorMsgHolder  The field for the parse errors.
356     */
357    private final void createRetrieveParseErrorMessage( final FieldSpec errorMsgHolder )
358    {
359        final var typeName = ParameterizedTypeName.from( Optional.class, String.class );
360        final var method = getComposer().methodBuilder( "retrieveParseErrorMessage" )
361            .addModifiers( PUBLIC, FINAL )
362            .addAnnotation( Override.class )
363            .returns( typeName )
364            .addJavadoc( getComposer().createInheritDocComment() )
365            .addStatement( "return $T.ofNullable( $N )", Optional.class, errorMsgHolder )
366            .build();
367        addMethod( method );
368    }   //  createRetrieveParseErrorMessage()
369
370    /**
371     *  Is called by
372     *  {@link #build()}
373     *  to do the work – only if there is work to do …
374     */
375    private final void doBuild()
376    {
377        //---* Create the registry for the CLI definitions *-------------------
378        final var registryType = ParameterizedTypeName.from( ClassName.from( List.class ), TypeName.from( CLIDefinition.class ) );
379        final var registry = getComposer().fieldBuilder( registryType, STD_FIELD_CLIDefinitions.toString(), PRIVATE, FINAL )
380            .addJavadoc(
381                """
382                The registry for the CLI definitions
383                """ )
384            .initializer( "new $T<>()", ArrayList.class )
385            .build();
386        addField( STD_FIELD_CLIDefinitions, registry );
387
388        //---* Create the field for the CLI parsing errors *-------------------
389        final var errorMsgHolder = getComposer().fieldBuilder( String.class, STD_FIELD_CLIError.toString(), PRIVATE )
390            .addJavadoc(
391                """
392                The last error message from a call to
393                {@link #parseCommandLine(String[])}.
394
395                @see #retrieveParseErrorMessage()
396                """ )
397            .initializer( "null" )
398            .build();
399        addField( STD_FIELD_CLIError, errorMsgHolder );
400
401        //---* Add the methods from CLIBeanSpec *------------------------------
402        createDumpParamFileTemplate();
403        createParseCommandLine( registry, errorMsgHolder );
404        createPrintUsage( registry );
405        createRetrieveParseErrorMessage( errorMsgHolder );
406
407        /*
408         * The names of previously encountered options, collected to avoid
409         * collisions.
410         */
411        final Collection<String> alreadyUsedOptions = new HashSet<>();
412
413        //---* Add the code to the constructor *-------------------------------
414        final var objectType = WildcardTypeName.subtypeOf( Object.class );
415        final var handlerName = "valueHandler";
416        final var handlerType = ParameterizedTypeName.from( ClassName.from( CmdLineValueHandler.class ), objectType );
417
418        final var definitionName = "cliDefinition";
419
420        final var builder = getComposer().codeBlockBuilder().add(
421                """
422    
423                /*
424                 * Initialise the CLI definitions.
425                 */
426                """ )
427            .addStatement( "$T $L", handlerType, handlerName )
428            .addStatement( "$T $L", CLIDefinition.class, definitionName );
429
430        CLIPropertiesLoop:
431        for( final var iterator = getProperties(); iterator.hasNext(); )
432        {
433            final var property = iterator.next();
434            if( !property.isOnCLI() ) continue CLIPropertiesLoop;
435
436            //---* Create the value handler *----------------------------------
437            builder.add(
438                    """
439                    
440                    /*
441                     * CLI definition for Property &quot;$L&quot;.
442                     */
443                    """, property.getPropertyName()
444                )
445                .addStatement( "$L = $L()", handlerName, composeValueHandlerCreation( property ) );
446
447            //---* Create the CLI definition *---------------------------------
448            final var usage = property.getCLIUsage().orElse( null );
449            final var usageKey = property.getCLIUsageKey().orElse( null );
450            final var metaVar = property.getCLIMetaVar().orElse( null );
451            final var required = Boolean.valueOf( property.hasFlag( PROPERTY_CLI_MANDATORY ) );
452            final var multiValued = Boolean.valueOf( property.hasFlag( PROPERTY_CLI_MULTIVALUED ) );
453            final var format = property.getCLIFormat().orElse( null );
454            if( property.hasFlag( PROPERTY_IS_ARGUMENT ) )
455            {
456                builder.addStatement( "$1L = new $2T( $3S, $4L, $5S, $6S, $7S, $8L, $9L, $10L, $11S )",
457                    definitionName,
458                    CLIArgumentDefinition.class,
459                    property.getPropertyName(),
460                    Integer.valueOf( property.getCLIArgumentIndex().orElseThrow( () -> new IllegalAnnotationError( format( MSG_NoArgumentIndex, property.getPropertyName() ) ) ) ),
461                    usage,
462                    usageKey,
463                    metaVar,
464                    required,
465                    handlerName,
466                    multiValued,
467                    format );
468            }
469            else if( property.hasFlag( PROPERTY_IS_OPTION ) )
470            {
471                final var optionNames = property.getCLIOptionNames().orElseThrow( () -> new IllegalAnnotationError( format( MSG_NoOptionName, property.getPropertyName() ) ) );
472                for( final var optionName : optionNames )
473                {
474                    if( !alreadyUsedOptions.add( optionName ) )
475                    {
476                        throw new IllegalAnnotationError( format( MSG_DuplicateOptionName, optionName, property.getPropertyName() ) );
477                    }
478                }
479                final var names = optionNames.stream()
480                    .map( s -> format( "\"%s\"", s ) )
481                    .collect( joining( ", ", "List.of( ", " )" ) );
482                builder.addStatement( "$1L = new $2T( $3S, $4L, $5S, $6S, $7S, $8L, $9L, $10L, $11S )",
483                    definitionName,
484                    CLIOptionDefinition.class,
485                    property.getPropertyName(),
486                    names,
487                    usage,
488                    usageKey,
489                    metaVar,
490                    required,
491                    handlerName,
492                    multiValued,
493                    format );
494            }
495            else
496            {
497                throw new IllegalAnnotationError( format( MSG_InvalidCLIType, property.getPropertyName() ) );
498            }
499            builder.addStatement( "$N.add( $L )", registry, definitionName );
500        }
501        addConstructorCode( builder.build() );
502    }   //  doBuild()
503
504    /**
505     *  <p>{@summary Retrieves the class for the value handler for the given
506     *  property.} If returning
507     *  {@link Optional#empty()} empty},
508     *  an instance of
509     *  {@link SimpleCmdLineValueHandler}
510     *  must be used that will be instantiated with the
511     *  {@link org.tquadrat.foundation.lang.StringConverter}
512     *  retrieved by a call to
513     *  {@link PropertySpec#getStringConverterClass()}.</p>
514     *
515     *  @param  property    The property.
516     *  @return An instance of
517     *      {@link Optional}
518     *      that holds the respective type name.
519     */
520    private final Optional<TypeName> retrieveValueHandlerClass( final PropertySpec property )
521    {
522        var retValue = requireNonNullArgument( property, "property" ).getCLIValueHandlerClass();
523        if( retValue.isEmpty() )
524        {
525            final var handlerClass = m_HandlerClasses.get( property.getPropertyType() );
526            if( nonNull( handlerClass ) )
527            {
528                retValue = Optional.of( handlerClass );
529            }
530        }
531        ifDebug( a ->
532        {
533            final var propertyName = ((PropertySpec) a [0]).getPropertyName();
534            final var propertyType = ((PropertySpec) a [0]).getPropertyType().toString();
535            //noinspection unchecked
536            final var handlerClass = Objects.toString( ((Optional<TypeName>) a [1]).orElse( null ) );
537            return format( "property: %1$s%n\tpropertyType: %2$s%n\thandlerClass: %3$s", propertyName, propertyType, handlerClass );
538        }, property, retValue );
539
540        //---* Done *----------------------------------------------------------
541        return retValue;
542    }   //  retrieveValueHandlerClass()
543}
544//  class CLIBeanBuilder
545
546/*
547 *  End of File
548 */