001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 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.lang.reflect.Modifier.isPublic; 022import static java.lang.reflect.Modifier.isStatic; 023import static javax.lang.model.element.Modifier.FINAL; 024import static javax.lang.model.element.Modifier.PRIVATE; 025import static javax.lang.model.element.Modifier.PUBLIC; 026import static org.apiguardian.api.API.Status.INTERNAL; 027import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.DEFAULT_ACCESSOR_TYPE; 028import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.ENUM_ACCESSOR_TYPE; 029import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.LIST_ACCESSOR_TYPE; 030import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MAP_ACCESSOR_TYPE; 031import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_AccessorMissing; 032import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_MissingEnvironmentVar; 033import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_MissingStringConverter; 034import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_MissingStringConverterWithType; 035import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_MissingSystemProp; 036import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_NoCollection; 037import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_PreferencesNotConfigured; 038import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.SET_ACCESSOR_TYPE; 039import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.ALLOWS_PREFERENCES; 040import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.GETTER_RETURNS_OPTIONAL; 041import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_ARGUMENT; 042import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_MUTABLE; 043import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_OPTION; 044import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_REQUIRES_SYNCHRONIZATION; 045import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.SETTER_CHECK_EMPTY; 046import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.SETTER_CHECK_NULL; 047import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.SYSTEM_PREFERENCE; 048import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_ListenerSupport; 049import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_ReadLock; 050import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_WriteLock; 051import static org.tquadrat.foundation.config.ap.impl.codebuilders.CodeBuilderBase.StringConverterInstantiation.AS_ENUM; 052import static org.tquadrat.foundation.config.ap.impl.codebuilders.CodeBuilderBase.StringConverterInstantiation.BY_INSTANCE; 053import static org.tquadrat.foundation.config.ap.impl.codebuilders.CodeBuilderBase.StringConverterInstantiation.THROUGH_CONSTRUCTOR; 054import static org.tquadrat.foundation.javacomposer.Primitives.VOID; 055import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING; 056import static org.tquadrat.foundation.lang.Objects.nonNull; 057import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 058 059import java.io.IOException; 060import java.util.ArrayList; 061import java.util.EnumMap; 062import java.util.HashMap; 063import java.util.HashSet; 064import java.util.Iterator; 065import java.util.List; 066import java.util.Map; 067import java.util.Optional; 068import java.util.Set; 069import java.util.prefs.BackingStoreException; 070import java.util.prefs.Preferences; 071import java.util.stream.Stream; 072 073import org.apiguardian.api.API; 074import org.tquadrat.foundation.annotation.ClassVersion; 075import org.tquadrat.foundation.ap.CodeGenerationError; 076import org.tquadrat.foundation.config.ap.CodeGenerationConfiguration; 077import org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor; 078import org.tquadrat.foundation.config.ap.PropertySpec; 079import org.tquadrat.foundation.config.ap.impl.CodeBuilder; 080import org.tquadrat.foundation.config.ap.impl.PropertySpecImpl; 081import org.tquadrat.foundation.exception.UnsupportedEnumError; 082import org.tquadrat.foundation.javacomposer.ClassName; 083import org.tquadrat.foundation.javacomposer.CodeBlock; 084import org.tquadrat.foundation.javacomposer.FieldSpec; 085import org.tquadrat.foundation.javacomposer.JavaComposer; 086import org.tquadrat.foundation.javacomposer.MethodSpec; 087import org.tquadrat.foundation.javacomposer.ParameterSpec; 088import org.tquadrat.foundation.javacomposer.ParameterizedTypeName; 089import org.tquadrat.foundation.javacomposer.SuppressableWarnings; 090import org.tquadrat.foundation.javacomposer.TypeName; 091import org.tquadrat.foundation.javacomposer.TypeSpec; 092import org.tquadrat.foundation.lang.Objects; 093import org.tquadrat.foundation.lang.StringConverter; 094 095/** 096 * The abstract base class for all the code builders. 097 * 098 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 099 * @version $Id: CodeBuilderBase.java 1105 2024-02-28 12:58:46Z tquadrat $ 100 * @UMLGraph.link 101 * @since 0.1.0 102 */ 103@SuppressWarnings( {"OverlyCoupledClass", "OverlyComplexClass"} ) 104@ClassVersion( sourceVersion = "$Id: CodeBuilderBase.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 105@API( status = INTERNAL, since = "0.1.0" ) 106abstract sealed class CodeBuilderBase implements CodeBuilder 107 permits CLIBeanBuilder, ConfigBeanBuilder, I18nSupportBuilder, INIBeanBuilder, MapImplementor, PreferencesBeanBuilder, SessionBeanBuilder 108{ 109 /*---------------*\ 110 ====** Inner Classes **==================================================== 111 \*---------------*/ 112 /** 113 * The various type to instantiate a 114 * {@link StringConverter} 115 * class. 116 * 117 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 118 * @version $Id: CodeBuilderBase.java 1105 2024-02-28 12:58:46Z tquadrat $ 119 * @UMLGraph.link 120 * @since 0.1.0 121 */ 122 @ClassVersion( sourceVersion = "$Id: CodeBuilderBase.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 123 @API( status = INTERNAL, since = "0.1.0" ) 124 public static enum StringConverterInstantiation 125 { 126 /** 127 * The 128 * {@link StringConverter} 129 * can be accessed through the {@code INSTANCE} field. 130 */ 131 BY_INSTANCE, 132 133 /** 134 * The 135 * {@link StringConverter} 136 * has to be instantiated by calling its default constructor. 137 */ 138 THROUGH_CONSTRUCTOR, 139 140 /** 141 * The property is an 142 * {@link Enum enum}, 143 * and the class for the 144 * {@link StringConverter} 145 * is 146 * {@link org.tquadrat.foundation.util.stringconverter.EnumStringConverter}, 147 * so the {@code StringConverter} has to be instantiated by a call to 148 * {@link org.tquadrat.foundation.util.stringconverter.EnumStringConverter#EnumStringConverter(Class)}. 149 */ 150 AS_ENUM 151 } 152 // enum StringConverterInstantiation 153 154 155 /*------------*\ 156 ====** Attributes **======================================================= 157 \*------------*/ 158 /** 159 * The class builder. 160 */ 161 private final TypeSpec.Builder m_ClassBuilder; 162 163 /** 164 * The composer. 165 */ 166 @SuppressWarnings( "UseOfConcreteClass" ) 167 private final JavaComposer m_Composer; 168 169 /** 170 * The configuration for the code generation. 171 */ 172 @SuppressWarnings( "UseOfConcreteClass" ) 173 private final CodeGenerationConfiguration m_Configuration; 174 175 /** 176 * The builder for body of the constructor. 177 */ 178 private final CodeBlock.Builder m_ConstructorCode; 179 180 /** 181 * The code generator context. 182 */ 183 private final CodeGeneratorContext m_Context; 184 185 /** 186 * The synchronised flag. 187 */ 188 private final boolean m_IsSynchronized; 189 190 /** 191 * The standard fields. 192 */ 193 @SuppressWarnings( "StaticCollection" ) 194 private static final Map<StandardField,FieldSpec> m_StandardFields = new EnumMap<>( StandardField.class ); 195 196 /** 197 * The standard methods. 198 */ 199 @SuppressWarnings( "StaticCollection" ) 200 private static final Map<StandardMethod,MethodSpec> m_StandardMethods = new EnumMap<>( StandardMethod.class ); 201 202 /*------------------------*\ 203 ====** Static Initialisations **=========================================== 204 \*------------------------*/ 205 /** 206 * The registry for the known implementations of 207 * {@link StringConverter} 208 * implementations. 209 */ 210 @SuppressWarnings( "StaticCollection" ) 211 private static final Map<TypeName,ClassName> m_ConverterRegistry; 212 213 static 214 { 215 try 216 { 217 m_ConverterRegistry = Map.copyOf( ConfigAnnotationProcessor.createStringConverterRegistry() ); 218 } 219 catch( final IOException e ) 220 { 221 throw new ExceptionInInitializerError( e ); 222 } 223 } 224 225 /*--------------*\ 226 ====** Constructors **===================================================== 227 \*--------------*/ 228 /** 229 * Creates a new instance of {@code CodeBuilderBase}. 230 * 231 * @param context The code generator context. 232 */ 233 protected CodeBuilderBase( final CodeGeneratorContext context ) 234 { 235 m_Context = requireNonNullArgument( context, "context" ); 236 237 m_Configuration = m_Context.getConfiguration(); 238 m_Composer = m_Configuration.getComposer(); 239 m_ClassBuilder = m_Context.getClassBuilder(); 240 m_ConstructorCode = m_Context.getConstructorCodeBuilder(); 241 242 m_IsSynchronized = m_Configuration.getSynchronizationRequired(); 243 244 m_Configuration.getInitDataMethod().ifPresent( spec -> m_StandardMethods.put( StandardMethod.STD_METHOD_InitData, spec ) ); 245 } // CodeBuilderBase() 246 247 /*---------*\ 248 ====** Methods **========================================================== 249 \*---------*/ 250 /** 251 * Adds an argument to the constructor. 252 * 253 * @param argument The parameter to add. 254 */ 255 protected final void addConstructorArgument( final ParameterSpec argument ) 256 { 257 final var constructorBuilder = m_Context.getConstructorBuilder(); 258 constructorBuilder.addParameter( requireNonNullArgument( argument, "argument" ) ); 259 } // addConstructorArgument() 260 261 /** 262 * Adds code to the constructor body. 263 * 264 * @param code The code to add. 265 */ 266 protected final void addConstructorCode( final CodeBlock code ) 267 { 268 m_ConstructorCode.add( requireNonNullArgument( code, "code" ) ); 269 } // addConstructorCode() 270 271 /** 272 * Adds a warning to the 273 * {@link java.lang.SuppressWarnings @SuppressWarnings} 274 * annotation for the constructor of the new configuration bean. 275 * 276 * @param warning The warning to suppress. 277 */ 278 protected final void addConstructorSuppressedWarning( final SuppressableWarnings warning ) 279 { 280 m_Context.addConstructorSuppressedWarning( warning ); 281 } // addConstructorSuppressedWarning() 282 283 /** 284 * Adds the given field to the new class. 285 * 286 * @param field The field to add. 287 */ 288 protected final void addField( final FieldSpec field ) 289 { 290 m_ClassBuilder.addField( requireNonNullArgument( field, "field" ) ); 291 } // addField() 292 293 /** 294 * Adds the given standard field to the new class. 295 * 296 * @param reference The identifier for the standard field. 297 * @param field The field to add. 298 */ 299 protected final void addField( final StandardField reference, final FieldSpec field ) 300 { 301 addField( field ); 302 m_StandardFields.put( requireNonNullArgument( reference, "reference" ), field ); 303 } // addField() 304 305 /** 306 * Adds the given method to the new class. 307 * 308 * @param method The method to add. 309 */ 310 protected final void addMethod( final MethodSpec method ) 311 { 312 m_ClassBuilder.addMethod( requireNonNullArgument( method, "method" ) ); 313 } // addMethod() 314 315 /** 316 * Adds the given method to the new class. 317 * 318 * @param reference The identifier for the standard method. 319 * @param method The method to add. 320 */ 321 protected final void addMethod( final StandardMethod reference, final MethodSpec method ) 322 { 323 addMethod( method ); 324 m_StandardMethods.put( requireNonNullArgument( reference, "reference" ), method ); 325 } // addMethod() 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override 331 public abstract void build(); 332 333 /** 334 * The default implementation of the method that composes an 'add' method 335 * for the given property. 336 * 337 * @param codeBuilder The factory for the code generation. 338 * @param property The property. 339 * @return The method specification. 340 */ 341 @SuppressWarnings( {"OptionalGetWithoutIsPresent", "EnhancedSwitchMigration", "UseOfConcreteClass", "StaticMethodOnlyUsedInOneClass", "OverlyCoupledMethod", "OverlyComplexMethod"} ) 342 public static MethodSpec composeAddMethod( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 343 { 344 final var composer = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer(); 345 346 //---* Obtain the builder *-------------------------------------------- 347 final var builder = property.getAddMethodBuilder() 348 .orElseGet( () -> composer.methodBuilder( property.getAddMethodName().get() ) 349 .addAnnotation( Override.class ) 350 .addModifiers( PUBLIC ) 351 .returns( VOID ) 352 ); 353 builder.addModifiers( FINAL ) 354 .addJavadoc( composer.createInheritDocComment() ); 355 356 //---* Add the locking *----------------------------------------------- 357 final var lock = property.hasFlag( PROPERTY_REQUIRES_SYNCHRONIZATION ) ? codeBuilder.getField( STD_FIELD_WriteLock ) : null; 358 if( nonNull( lock) ) builder.beginControlFlow( 359 """ 360 try( final var l = $N.lock() ) 361 """, lock ); 362 363 //---* Assign the value *---------------------------------------------- 364 final var argumentType = switch( property.getCollectionKind() ) 365 { 366 case LIST, SET -> 367 { 368 if( property.getPropertyType() instanceof final ParameterizedTypeName propertyType ) 369 { 370 final var typeArguments = propertyType.typeArguments(); 371 yield typeArguments.getFirst(); 372 } 373 yield ClassName.from( Object.class ); 374 } 375 376 case MAP -> 377 { 378 TypeName keyType = ClassName.from( Object.class ); 379 TypeName valueType = ClassName.from( Object.class ); 380 if( property.getPropertyType() instanceof final ParameterizedTypeName propertyType ) 381 { 382 final var typeArguments = propertyType.typeArguments(); 383 keyType = typeArguments.get( 0 ); 384 valueType = typeArguments.get( 1 ); 385 } 386 final var entryType = ClassName.from( Map.Entry.class ); 387 yield ParameterizedTypeName.from( entryType, keyType, valueType ); 388 } 389 390 case NO_COLLECTION -> throw new CodeGenerationError( format( MSG_NoCollection, property.getAddMethodName().get(), property.getPropertyName() ) ); 391 392 default -> throw new UnsupportedEnumError( property.getCollectionKind() ); 393 }; 394 395 //---* Create the parameter *------------------------------------------ 396 final var parameter = composer.parameterOf( argumentType, property.getAddMethodArgumentName(), FINAL ); 397 builder.addParameter( parameter ); 398 399 //---* Obtain the field *---------------------------------------------- 400 final var field = property.getFieldName(); 401 402 //---* Create the code *----------------------------------------------- 403 builder.addStatement( "$T oldValue = null", property.getPropertyType() ) 404 .beginControlFlow( 405 """ 406 if( isNull( $N ) ) 407 """, field ) 408 .addStaticImport( Objects.class, "isNull" ); 409 switch( property.getCollectionKind() ) 410 { 411 case LIST: 412 { 413 builder.addStatement( "$1N = new $2T<>()", field, ArrayList.class ) 414 .nextControlFlow( 415 """ 416 417 else 418 """ ) 419 .addStatement( "oldValue = $1T.copyOf( $2N )", List.class, field ) 420 .endControlFlow() 421 .addStatement( "$1N.add( requireNonNullArgument( $2N, $3S ) )", field, parameter, property.getAddMethodArgumentName() ) 422 .addStaticImport( Objects.class, "requireNonNullArgument" ) 423 .addStatement( "$1N.fireEvent( $2S, oldValue, $3T.copyOf( $4N ) )", codeBuilder.getField( STD_FIELD_ListenerSupport ), property.getPropertyName(), List.class, field ); 424 break; 425 } 426 427 case MAP: 428 { 429 builder.addStatement( "$1N = new $2T<>()", field, HashMap.class ) 430 .nextControlFlow( 431 """ 432 433 else 434 """ ) 435 .addStatement( "oldValue = $T.copyOf( $N )", Map.class, field ) 436 .endControlFlow() 437 .addStatement( "var key = requireNonNullArgument( $1N, $2S ).getKey()", parameter, property.getAddMethodArgumentName() ) 438 .addStaticImport( Objects.class, "requireNonNullArgument" ) 439 .addStatement( "var value = $N.getValue()", parameter ) 440 .addStatement( "$1N.put( requireNonNullArgument( key, $2S + \".key\" ), requireNonNullArgument( value, $2S + \".value\" ) )", field, property.getAddMethodArgumentName() ) 441 .addStatement( "$1N.fireEvent( $2S, oldValue, $3T.copyOf( $4N ) )", codeBuilder.getField( STD_FIELD_ListenerSupport ), property.getPropertyName(), Map.class, field ); 442 break; 443 } 444 445 case SET: 446 { 447 builder.addStatement( "$1N = new $2T<>()", field, HashSet.class ) 448 .nextControlFlow( 449 """ 450 451 else 452 """ ) 453 .addStatement( "oldValue = $1T.copyOf( $2N )", Set.class, field ) 454 .endControlFlow() 455 .addStatement( "$1N.add( requireNonNullArgument( $2N, $3S ) )", field, parameter, property.getAddMethodArgumentName() ) 456 .addStaticImport( Objects.class, "requireNonNullArgument" ) 457 .addStatement( "$1N.fireEvent( $2S, oldValue, $3T.copyOf( $4N ) )", codeBuilder.getField( STD_FIELD_ListenerSupport ), property.getPropertyName(), Set.class, field ); 458 break; 459 } 460 461 case NO_COLLECTION: throw new CodeGenerationError( format( MSG_NoCollection, property.getAddMethodName().get(), property.getPropertyName() ) ); 462 463 default: throw new UnsupportedEnumError( property.getCollectionKind() ); 464 } 465 builder.endControlFlow(); 466 467 //---* Cleanup *------------------------------------------------------- 468 if( nonNull( lock) ) builder.endControlFlow(); 469 470 //---* Create the return value *--------------------------------------- 471 final var retValue = builder.build(); 472 473 //---* Done *---------------------------------------------------------- 474 return retValue; 475 } // composeAddMethod() 476 477 /** 478 * The default implementation of the method that composes a constructor 479 * fragment for the initialisation of the given property in cases it is 480 * annotated with 481 * {@link org.tquadrat.foundation.config.EnvironmentVariable @EnvironmentVariable}. 482 * 483 * @param codeBuilder The factory for the code generation. 484 * @param property The property. 485 * @return The field specification. 486 */ 487 @SuppressWarnings( {"UseOfConcreteClass", "TypeMayBeWeakened", "StaticMethodOnlyUsedInOneClass"} ) 488 public static CodeBlock composeConstructorFragment4Environment( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 489 { 490 final var builder = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer() 491 .codeBlockBuilder() 492 .add( 493 """ 494 495 /* 496 * Initialise the property '$N' from the system environment. 497 */ 498 """, property.getPropertyName() 499 ) 500 .beginControlFlow( EMPTY_STRING ); 501 502 //---* Set the StringConverter *--------------------------------------- 503 final var stringConverter = property.getStringConverterClass() 504 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverter, property.getPropertyName() ) ) ); 505 switch( determineStringConverterInstantiation( stringConverter, property.isEnum() ) ) 506 { 507 case BY_INSTANCE -> builder.addStatement( "final var stringConverter = $T.INSTANCE", stringConverter ); 508 case THROUGH_CONSTRUCTOR -> builder.addStatement( "final var stringConverter = new $T()", stringConverter ); 509 case AS_ENUM -> builder.addStatement( "final var stringConverter = new $1T( $2T.class )", stringConverter, property.getPropertyType() ); 510 } 511 512 //---* Set the value *------------------------------------------------- 513 final var defaultValue = property.getEnvironmentDefaultValue(); 514 if( defaultValue.isPresent() ) 515 { 516 builder.addStatement( "var value = getenv( $1S )", property.getEnvironmentVariableName().orElseThrow( () -> new CodeGenerationError( format( MSG_MissingEnvironmentVar, property.getPropertyName() ) ) ) ) 517 .beginControlFlow( 518 """ 519 if( isNull( value ) ) 520 """ 521 ) 522 .addStaticImport( Objects.class, "isNull" ) 523 .addStaticImport( System.class, "getenv" ) 524 .addStatement( "value = $1S", defaultValue.get() ) 525 .endControlFlow(); 526 } 527 else 528 { 529 builder.addStatement( "final var value = getenv( $1S )", property.getEnvironmentVariableName().orElseThrow( () -> new CodeGenerationError( format( MSG_MissingEnvironmentVar, property.getPropertyName() ) ) ) ); 530 } 531 builder.addStaticImport( System.class, "getenv" ) 532 .addStaticImport( System.class, "getenv" ) 533 .addStatement( "$1N = stringConverter.fromString( value )", property.getFieldName() ) 534 .endControlFlow(); 535 536 //---* Create the return value *--------------------------------------- 537 final var retValue = builder.build(); 538 539 //---* Done *---------------------------------------------------------- 540 return retValue; 541 } // composeConstructorFragment4Environment() 542 543 /** 544 * The default implementation of the method that composes a constructor 545 * fragment for the initialisation of the given property in cases it is 546 * annotated with 547 * {@link org.tquadrat.foundation.config.EnvironmentVariable @EnvironmentVariable}. 548 * 549 * @param codeBuilder The factory for the code generation. 550 * @param property The property. 551 * @return The field specification. 552 */ 553 @SuppressWarnings( {"UseOfConcreteClass", "StaticMethodOnlyUsedInOneClass", "OverlyComplexMethod"} ) 554 public static CodeBlock composeConstructorFragment4SystemPreference( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 555 { 556 final var composer = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer(); 557 558 /* 559 * Create the lambdas for the getter and the setter. 560 * The getter is not used, but required for the constructor of the 561 * PropertyAccess instance. 562 */ 563 final var getter = composer.lambdaBuilder() 564 .addCode( "$N", requireNonNullArgument( property, "property" ).getFieldName() ) 565 .build(); 566 final var setter = composer.lambdaBuilder() 567 .addParameter( "p" ) 568 .addCode( "$N = p", property.getFieldName() ) 569 .build(); 570 571 //---* The accessor class *-------------------------------------------- 572 final var accessorClass = property.getPrefsAccessorClass() 573 .orElseThrow( () -> new CodeGenerationError( format( MSG_AccessorMissing, property.getPropertyName() ) ) ); 574 575 //---* The path to the node *------------------------------------------ 576 final var path = property.getSystemPrefsPath() 577 .orElseThrow( () -> new CodeGenerationError( format( MSG_PreferencesNotConfigured, property.getPropertyName() ) ) ); 578 579 //---* The key for the value *----------------------------------------- 580 final var key = property.getPrefsKey() 581 .orElseThrow( () -> new CodeGenerationError( format( MSG_PreferencesNotConfigured, property.getPropertyName() ) ) ); 582 583 final var builder = composer 584 .codeBlockBuilder() 585 .add( 586 """ 587 588 /* 589 * Initialise the property '$N' from the SYSTEM {@code Preferences}. 590 * 591 * Path: $L 592 * Key : $L 593 */ 594 """, property.getPropertyName(), path, key 595 ) 596 .beginControlFlow( 597 """ 598 try 599 """ ) 600 .beginControlFlow( 601 """ 602 if( systemRoot().nodeExists( $S ) ) 603 """, path ) 604 .addStaticImport( Preferences.class, "systemRoot" ) 605 .addStatement( "final var node = systemRoot().node( $S )", path ); 606 607 //noinspection IfStatementWithTooManyBranches 608 if( accessorClass.equals( ENUM_ACCESSOR_TYPE ) ) 609 { 610 final var propertyType = property.getPropertyType(); 611 builder.addStatement( "final var accessor = new $2T<>( $1S, $3T.class, $4L, $5L )", key, accessorClass, propertyType, getter, setter ); 612 } 613 else if( accessorClass.equals( LIST_ACCESSOR_TYPE ) || accessorClass.equals( SET_ACCESSOR_TYPE ) ) 614 { 615 final var propertyType = (ParameterizedTypeName) property.getPropertyType(); 616 final var argumentType = propertyType.typeArguments().getFirst(); 617 final var stringConverterType = getStringConverter( argumentType ) 618 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverterWithType, property.getPropertyName(), argumentType.toString() ) ) ); 619 switch( determineStringConverterInstantiation( stringConverterType, false ) ) 620 { 621 case BY_INSTANCE -> builder.addStatement( "final var accessor = new $2T<>( $1S, $3T.INSTANCE, $4L, $5L )", key, accessorClass, stringConverterType, getter, setter ); 622 case THROUGH_CONSTRUCTOR -> builder.addStatement( "final var accessor = new $2T<>( $1S, new $3T(), $4L, $5L )", key, accessorClass, stringConverterType, getter, setter ); 623 case AS_ENUM -> builder.addStatement( "final var accessor = new $2T<>( $1S, new $3T( $6T.class ), $4L, $5L )", key, accessorClass, stringConverterType, getter, setter, propertyType ); 624 } 625 } 626 else if( accessorClass.equals( MAP_ACCESSOR_TYPE ) ) 627 { 628 final var propertyType = (ParameterizedTypeName) property.getPropertyType(); 629 final var argumentTypes = propertyType.typeArguments(); 630 final var keyStringConverterType = getStringConverter( argumentTypes.getFirst() ) 631 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverterWithType, property.getPropertyName(), argumentTypes.getFirst().toString() ) ) ); 632 final var keySnippet = 633 switch( determineStringConverterInstantiation( keyStringConverterType, false ) ) 634 { 635 case BY_INSTANCE -> "$3T.INSTANCE"; 636 case THROUGH_CONSTRUCTOR -> "new $3T()"; 637 case AS_ENUM -> EMPTY_STRING; 638 }; 639 final var valueStringConverterType = getStringConverter( argumentTypes.get( 1 ) ) 640 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverterWithType, property.getPropertyName(), argumentTypes.get( 1 ).toString() ) ) ); 641 final var valueSnippet = 642 switch( determineStringConverterInstantiation( valueStringConverterType, false ) ) 643 { 644 case BY_INSTANCE -> "$4T.INSTANCE"; 645 case THROUGH_CONSTRUCTOR -> "new $4T()"; 646 case AS_ENUM -> EMPTY_STRING; 647 }; 648 builder.addStatement( format( "final var accessor = new $2T<>( $1S, %1$s, %2$s, $5L, $6L )", keySnippet, valueSnippet ), key, accessorClass, keyStringConverterType, valueStringConverterType, getter, setter ); 649 } 650 else if( accessorClass.equals( DEFAULT_ACCESSOR_TYPE ) ) 651 { 652 final var stringConverterType = property.getStringConverterClass() 653 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverter, property.getPropertyName() ) ) ); 654 switch( determineStringConverterInstantiation( stringConverterType, property.isEnum() ) ) 655 { 656 case BY_INSTANCE -> builder.addStatement( "final var accessor = new $2T<>( $1S, $4L, $5L, $3T.INSTANCE )", key, accessorClass, stringConverterType, getter, setter ); 657 case THROUGH_CONSTRUCTOR -> builder.addStatement( "final var accessor = new $2T<>( $1S, $4L, $5L, new $3T() )", key, accessorClass, stringConverterType, getter, setter ); 658 case AS_ENUM -> builder.addStatement( "final var accessor = new $2T<>( $1S, $4L, $5L, new $3T( $6T.class ) )", key, accessorClass, stringConverterType, getter, setter, property.getPropertyType() ); 659 } 660 } 661 else 662 { 663 builder.addStatement( "final var accessor = new $2T( $1S, $3L, $4L )", key, accessorClass, getter, setter ); 664 } 665 666 //---* Add the code to read the Preferences *-------------------------- 667 builder.addStatement( "accessor.readPreference( node )" ) 668 .endControlFlow() 669 .nextControlFlow( 670 """ 671 672 catch( final $T e ) 673 """, BackingStoreException.class ) 674 .addStatement( "throw new $T( e )", ExceptionInInitializerError.class ) 675 .endControlFlow(); 676 677 //---* Create the return value *--------------------------------------- 678 final var retValue = builder.build(); 679 680 //---* Done *---------------------------------------------------------- 681 return retValue; 682 } // composeConstructorFragment4SystemPreference() 683 684 /** 685 * The default implementation of the method that composes a constructor 686 * fragment for the initialisation of the given property in cases it is 687 * annotated with 688 * {@link org.tquadrat.foundation.config.EnvironmentVariable @EnvironmentVariable}. 689 * 690 * @param codeBuilder The factory for the code generation. 691 * @param property The property. 692 * @return The field specification. 693 */ 694 @SuppressWarnings( {"UseOfConcreteClass", "TypeMayBeWeakened", "StaticMethodOnlyUsedInOneClass"} ) 695 public static CodeBlock composeConstructorFragment4SystemProp( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 696 { 697 final var builder = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer() 698 .codeBlockBuilder() 699 .add( 700 """ 701 702 /* 703 * Initialise the property '$N' from the system properties. 704 */ 705 """, property.getPropertyName() 706 ) 707 .beginControlFlow( EMPTY_STRING ); 708 709 //---* Set the StringConverter *--------------------------------------- 710 final var stringConverter = property.getStringConverterClass() 711 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverter, property.getPropertyName() ) ) ); 712 switch( determineStringConverterInstantiation( stringConverter, property.isEnum() ) ) 713 { 714 case BY_INSTANCE -> builder.addStatement( "final var stringConverter = $T.INSTANCE", stringConverter ); 715 case THROUGH_CONSTRUCTOR -> builder.addStatement( "final var stringConverter = new $T()", stringConverter ); 716 case AS_ENUM -> builder.addStatement( "final var stringConverter = new $1T( $2T.class )", stringConverter, property.getPropertyType() ); 717 } 718 719 //---* Set the value *------------------------------------------------- 720 final var defaultValue = property.getEnvironmentDefaultValue(); 721 if( defaultValue.isPresent() ) 722 { 723 builder.addStatement( "final var value = getProperty( $1S, $2S )", property.getSystemPropertyName().orElseThrow( () -> new CodeGenerationError( format( MSG_MissingSystemProp, property.getPropertyName() ) ) ), defaultValue.get() ); 724 } 725 else 726 { 727 builder.addStatement( "final var value = getProperty( $1S )", property.getSystemPropertyName().orElseThrow( () -> new CodeGenerationError( format( MSG_MissingSystemProp, property.getPropertyName() ) ) ) ); 728 } 729 builder.addStaticImport( System.class, "getProperty" ) 730 .addStatement( "$1N = stringConverter.fromString( value )", property.getFieldName() ) 731 .endControlFlow(); 732 733 //---* Create the return value *--------------------------------------- 734 final var retValue = builder.build(); 735 736 //---* Done *---------------------------------------------------------- 737 return retValue; 738 } // composeConstructorFragment4SystemProp() 739 740 /** 741 * The default implementation of the method that composes a field for the 742 * given property. 743 * 744 * @param codeBuilder The factory for the code generation. 745 * @param property The property. 746 * @return The field specification. 747 */ 748 @SuppressWarnings( {"UseOfConcreteClass", "StaticMethodOnlyUsedInOneClass"} ) 749 public static FieldSpec composeField( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 750 { 751 final var composer = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer(); 752 753 final var builder = composer.fieldBuilder( property.getPropertyType(), property.getFieldName(), PRIVATE ) 754 .addJavadoc( 755 """ 756 Property: "$L". 757 """, property.getPropertyName() ); 758 if( Stream.of( PROPERTY_IS_MUTABLE, PROPERTY_IS_OPTION, PROPERTY_IS_ARGUMENT, ALLOWS_PREFERENCES, SYSTEM_PREFERENCE ).noneMatch( property::hasFlag ) ) 759 { 760 builder.addModifiers( FINAL ); 761 } 762 763 //---* Create the return value *-------------------------------------- 764 final var retValue = builder.build(); 765 766 //---* Done *---------------------------------------------------------- 767 return retValue; 768 } // composeField() 769 770 /** 771 * The default implementation of the method that composes a getter for the 772 * given property. 773 * 774 * @param codeBuilder The factory for the code generation. 775 * @param property The property. 776 * @return The method specification. 777 */ 778 @SuppressWarnings( {"OptionalGetWithoutIsPresent", "UseOfConcreteClass", "TypeMayBeWeakened", "StaticMethodOnlyUsedInOneClass"} ) 779 public static MethodSpec composeGetter( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 780 { 781 final var composer = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer(); 782 783 //---* Obtain the builder *-------------------------------------------- 784 final var builder = property.getGetterBuilder() 785 .orElseGet( () -> composer.methodBuilder( property.getGetterMethodName().get() ) 786 .addAnnotation( Override.class ) 787 .addModifiers( PUBLIC ) 788 .returns( property.getGetterReturnType() ) 789 ); 790 builder.addModifiers( FINAL ) 791 .addJavadoc( composer.createInheritDocComment() ); 792 793 //---* Add the locking *----------------------------------------------- 794 final var lock = property.hasFlag( PROPERTY_REQUIRES_SYNCHRONIZATION ) && property.hasFlag( PROPERTY_IS_MUTABLE ) 795 ? codeBuilder.getField( STD_FIELD_ReadLock ) 796 : null; 797 if( nonNull( lock) ) builder.beginControlFlow( 798 """ 799 try( final var ignored = $N.lock() ) 800 """, lock ); 801 802 //---* Return the value *---------------------------------------------- 803 if( property.hasFlag( GETTER_RETURNS_OPTIONAL ) ) 804 { 805 builder.addStatement( "return $1T.ofNullable( $2N )", Optional.class, property.getFieldName() ); 806 } 807 else 808 { 809 builder.addStatement( "return $1N", property.getFieldName() ); 810 } 811 812 //---* Cleanup *------------------------------------------------------- 813 if( nonNull( lock) ) builder.endControlFlow(); 814 815 //---* Create the return value *--------------------------------------- 816 final var retValue = builder.build(); 817 818 //---* Done *---------------------------------------------------------- 819 return retValue; 820 } // composeGetter() 821 822 /** 823 * The default implementation of the method that composes a setter for the 824 * given property. 825 * 826 * @param codeBuilder The factory for the code generation. 827 * @param property The property. 828 * @return The method specification. 829 */ 830 @SuppressWarnings( {"OptionalGetWithoutIsPresent", "UseOfConcreteClass", "TypeMayBeWeakened", "StaticMethodOnlyUsedInOneClass"} ) 831 public static MethodSpec composeSetter( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 832 { 833 final var composer = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer(); 834 835 //---* Obtain the builder *-------------------------------------------- 836 final var builder = property.getSetterBuilder() 837 .orElseGet( () -> composer.methodBuilder( property.getSetterMethodName().get() ) 838 .addAnnotation( Override.class ) 839 .addModifiers( PUBLIC ) 840 .addParameter( composer.parameterOf( property.getPropertyType(), property.getSetterArgumentName(), FINAL ) ) 841 .returns( VOID ) 842 ); 843 builder.addModifiers( FINAL ) 844 .addJavadoc( composer.createInheritDocComment() ); 845 846 //---* Add the locking *----------------------------------------------- 847 final var lock = property.hasFlag( PROPERTY_REQUIRES_SYNCHRONIZATION ) ? codeBuilder.getField( STD_FIELD_WriteLock ) : null; 848 if( nonNull( lock) ) builder.beginControlFlow( 849 """ 850 try( final var ignored = $N.lock() ) 851 """, lock ); 852 853 //---* Assign the value *---------------------------------------------- 854 if( property.hasFlag( SETTER_CHECK_EMPTY ) ) 855 { 856 final var methodName = "requireNotEmptyArgument"; 857 builder.addStatement( 858 """ 859 final var newValue = $2N( $1N, $1S )\ 860 """, property.getSetterArgumentName(), methodName ) 861 .addStaticImport( Objects.class, methodName ); 862 } 863 else if( property.hasFlag( SETTER_CHECK_NULL ) ) 864 { 865 final var methodName = "requireNonNullArgument"; 866 builder.addStatement( 867 """ 868 final var newValue = $2N( $1N, $1S )\ 869 """, property.getSetterArgumentName(), methodName ) 870 .addStaticImport( Objects.class, methodName ); 871 } 872 else 873 { 874 builder.addStatement( 875 """ 876 final var newValue = $1N\ 877 """, property.getSetterArgumentName() ); 878 } 879 builder.addStatement( 880 """ 881 $1N.fireEvent( $2S, $3N, newValue )\ 882 """, codeBuilder.getField( STD_FIELD_ListenerSupport ), property.getPropertyName(), property.getFieldName() ) 883 .addStatement( 884 """ 885 $1N = newValue\ 886 """, property.getFieldName() ); 887 888 //---* Cleanup *------------------------------------------------------- 889 if( nonNull( lock) ) builder.endControlFlow(); 890 891 //---* Create the return value *--------------------------------------- 892 final var retValue = builder.build(); 893 894 //---* Done *---------------------------------------------------------- 895 return retValue; 896 } // composeSetter() 897 898 /** 899 * Determines how to instantiate the given implementation of 900 * {@link org.tquadrat.foundation.lang.StringConverter}. 901 * 902 * @param stringConverterClass The String converter class. 903 * @param isEnum {@code true} if the property is of an 904 * {@link Enum enum} type, {@code false} otherwise. 905 * @return The type of instantiation. 906 */ 907 protected static final StringConverterInstantiation determineStringConverterInstantiation( final TypeName stringConverterClass, final boolean isEnum ) 908 { 909 var retValue = THROUGH_CONSTRUCTOR; 910 if( requireNonNullArgument( stringConverterClass, "stringConverterClass" ) instanceof final ClassName className ) 911 { 912 try 913 { 914 final var candidateClass = Class.forName( className.canonicalName(), false, CodeBuilderBase.class.getClassLoader() ); 915 final var field = candidateClass.getField( "INSTANCE" ); 916 final var modifiers = field.getModifiers(); 917 retValue = field.canAccess( null ) && isPublic( modifiers ) && isStatic( modifiers ) ? BY_INSTANCE : THROUGH_CONSTRUCTOR; 918 } 919 catch( @SuppressWarnings( "unused" ) final NoSuchFieldException | SecurityException ignored ) 920 { 921 /* 922 * There is no INSTANCE field, or it is not static, or not public, 923 * or it is otherwise not accessible to this method. 924 */ 925 retValue = THROUGH_CONSTRUCTOR; 926 } 927 catch( @SuppressWarnings( "unused" ) final ClassNotFoundException ignored ) 928 { 929 /* 930 * The class for the StringConverter implementation does not 931 * exist. 932 */ 933 retValue = THROUGH_CONSTRUCTOR; 934 } 935 } 936 if( (retValue == THROUGH_CONSTRUCTOR) && isEnum ) 937 { 938 retValue = AS_ENUM; 939 } 940 941 //---* Done *---------------------------------------------------------- 942 return retValue; 943 } // determineStringConverterInstantiation 944 945 /** 946 * {@inheritDoc} 947 */ 948 @Override 949 public final JavaComposer getComposer() { return m_Composer; } 950 951 /** 952 * {@inheritDoc} 953 */ 954 @Override 955 public final CodeGenerationConfiguration getConfiguration() { return m_Configuration; } 956 957 /** 958 * {@inheritDoc} 959 */ 960 @Override 961 public final FieldSpec getField( final StandardField reference ) 962 { 963 final var retValue = m_StandardFields.get( requireNonNullArgument( reference, "reference" ) ); 964 965 //---* Done *---------------------------------------------------------- 966 return retValue; 967 } // getField() 968 969 /** 970 * {@inheritDoc} 971 */ 972 @Override 973 public final MethodSpec getMethod( final StandardMethod reference ) 974 { 975 final var retValue = m_StandardMethods.get( requireNonNullArgument( reference, "reference" ) ); 976 977 //---* Done *---------------------------------------------------------- 978 return retValue; 979 } // getMethod() 980 981 /** 982 * Returns an iterator over the configured properties. 983 * 984 * @return The iterator. 985 */ 986 protected final Iterator<PropertySpec> getProperties() { return m_Configuration.propertyIterator(); } 987 988 /** 989 * Returns the 990 * {@link StringConverter} 991 * type for the given type. 992 * 993 * @param type The type. 994 * @return An instance of 995 * {@link Optional} 996 * that holds the requested implementation of 997 * {@code StringConverter}. 998 */ 999 protected static final Optional<TypeName> getStringConverter( final TypeName type ) 1000 { 1001 final var retValue = Optional.ofNullable( (TypeName) m_ConverterRegistry.get( requireNonNullArgument( type, "type" ) ) ); 1002 1003 //---* Done *---------------------------------------------------------- 1004 return retValue; 1005 } // getStringConverter() 1006 1007 /** 1008 * Returns the flag that controls whether the configuration bean has to be 1009 * generated thread safe. 1010 * 1011 * @return {@code true} if lock support is required, {@code false} 1012 * otherwise. 1013 */ 1014 protected final boolean isSynchronized() { return m_IsSynchronized; } 1015} 1016// class CodeBuilderBase 1017 1018/* 1019 * End of File 1020 */