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 &#64;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 &#64;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 &#64;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 &#64;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: &quot;$L&quot;.
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 */