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 "$L.". 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 "$L". 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 */