001/*
002 * ============================================================================
003 *  Copyright © 2002-2023 by Thomas Thrien.
004 *  All Rights Reserved.
005 * ============================================================================
006 *  Licensed to the public under the agreements of the GNU Lesser General Public
007 *  License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *       http://www.gnu.org/licenses/lgpl.html
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 *  License for the specific language governing permissions and limitations
015 *  under the License.
016 */
017
018package org.tquadrat.foundation.config.ap.impl.codebuilders;
019
020import static javax.lang.model.element.Modifier.FINAL;
021import static javax.lang.model.element.Modifier.PRIVATE;
022import static javax.lang.model.element.Modifier.PUBLIC;
023import static javax.lang.model.element.Modifier.STATIC;
024import static org.apiguardian.api.API.Status.MAINTAINED;
025import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_RESOURCEBUNDLE;
026import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.ENVIRONMENT_VARIABLE;
027import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.EXEMPT_FROM_TOSTRING;
028import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.GETTER_IS_DEFAULT;
029import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_SPECIAL;
030import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.SYSTEM_PREFERENCE;
031import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.SYSTEM_PROPERTY;
032import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_ListenerSupport;
033import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_ReadLock;
034import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_WriteLock;
035import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardMethod.STD_METHOD_AddListener;
036import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardMethod.STD_METHOD_GetRessourceBundle;
037import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardMethod.STD_METHOD_RemoveListener;
038import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardMethod.STD_METHOD_ToString;
039import static org.tquadrat.foundation.javacomposer.Primitives.VOID;
040import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.INSTANCE_VARIABLE_OF_CONCRETE_CLASS;
041import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.THROW_CAUGHT_LOCALLY;
042import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.UNCHECKED;
043import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.createSuppressWarningsAnnotation;
044import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING;
045import static org.tquadrat.foundation.lang.Objects.nonNull;
046import static org.tquadrat.foundation.util.StringUtils.repeat;
047
048import java.io.FileNotFoundException;
049import java.io.IOException;
050import java.util.Properties;
051import java.util.StringJoiner;
052import java.util.concurrent.locks.ReentrantReadWriteLock;
053
054import org.apiguardian.api.API;
055import org.tquadrat.foundation.annotation.ClassVersion;
056import org.tquadrat.foundation.config.ConfigurationChangeListener;
057import org.tquadrat.foundation.config.ap.PropertySpec;
058import org.tquadrat.foundation.config.spi.ConfigChangeListenerSupport;
059import org.tquadrat.foundation.exception.ValidationException;
060import org.tquadrat.foundation.javacomposer.MethodSpec;
061import org.tquadrat.foundation.javacomposer.ParameterizedTypeName;
062import org.tquadrat.foundation.javacomposer.TypeName;
063import org.tquadrat.foundation.lang.AutoLock;
064import org.tquadrat.foundation.lang.CommonConstants;
065import org.tquadrat.foundation.lang.Objects;
066
067/**
068 *  The
069 *  {@linkplain org.tquadrat.foundation.config.ap.impl.CodeBuilder code builder implementation}
070 *  for the basic stuff, as defined in
071 *  {@link org.tquadrat.foundation.config.ConfigBeanSpec}.
072 *
073 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
074 *  @version $Id: ConfigBeanBuilder.java 1076 2023-10-03 18:36:07Z tquadrat $
075 *  @UMLGraph.link
076 *  @since 0.1.0
077 */
078@SuppressWarnings( "OverlyCoupledClass" )
079@ClassVersion( sourceVersion = "$Id: ConfigBeanBuilder.java 1076 2023-10-03 18:36:07Z tquadrat $" )
080@API( status = MAINTAINED, since = "0.1.0" )
081public final class ConfigBeanBuilder extends CodeBuilderBase
082{
083        /*--------------*\
084    ====** Constructors **=====================================================
085        \*--------------*/
086    /**
087     *  Creates a new instance of {@code ConfigBeanBuilder}.
088     *
089     *  @param  context The code generator context.
090     */
091    public ConfigBeanBuilder( final CodeGeneratorContext context )
092    {
093        super( context );
094    }   //  ConfigBeanBuilder()
095
096        /*---------*\
097    ====** Methods **==========================================================
098        \*---------*/
099    /**
100     *  Adds the listener support to the new class.
101     */
102    private final void addListenerSupport()
103    {
104        //---* Add the listener support *-------------------------------------
105        final var field = getComposer().fieldBuilder( ConfigChangeListenerSupport.class, STD_FIELD_ListenerSupport.toString() , PRIVATE, FINAL )
106            .addAnnotation( createSuppressWarningsAnnotation( getComposer(), INSTANCE_VARIABLE_OF_CONCRETE_CLASS ) )
107            .addJavadoc(
108                """
109                The support for the configuration change listener.
110                """ )
111            .build();
112        addField( STD_FIELD_ListenerSupport, field );
113
114        //---* Initialise the listener support *-------------------------------
115        var code = getComposer().codeBlockBuilder()
116            .add(
117                """
118                //---* Initialise the listener support *-------------------------------
119                """ )
120            .addStatement( "$N = new $T( this )", getField( STD_FIELD_ListenerSupport ), ConfigChangeListenerSupport.class )
121            .build();
122        addConstructorCode( code );
123
124        //---* Add the listener management methods *---------------------------
125        final var param1 = getComposer().parameterBuilder( ConfigurationChangeListener.class, "listener", FINAL )
126            .build();
127        code = getComposer().codeBlockBuilder()
128            .addStatement( "$N.addListener( $N )", getField( STD_FIELD_ListenerSupport ), param1 )
129            .build();
130        var method = getComposer().methodBuilder( STD_METHOD_AddListener.toString() )
131            .addModifiers( PUBLIC, FINAL )
132            .addAnnotation( Override.class )
133            .addJavadoc( getComposer().createInheritDocComment() )
134            .addParameter( param1 )
135            .returns( VOID )
136            .addCode( code )
137            .build();
138        addMethod( STD_METHOD_AddListener, method );
139
140        code = getComposer().codeBlockBuilder()
141            .addStatement( "$N.removeListener( $N )", getField( STD_FIELD_ListenerSupport ), param1 )
142            .build();
143        method = getComposer().methodBuilder( STD_METHOD_RemoveListener.toString() )
144            .addModifiers( PUBLIC, FINAL )
145            .addAnnotation( Override.class )
146            .addJavadoc( getComposer().createInheritDocComment() )
147            .addParameter( param1 )
148            .returns( VOID )
149            .addCode( code )
150            .build();
151        addMethod( STD_METHOD_RemoveListener, method );
152    }   //  addListenerSupport()
153
154    /**
155     *  Adds locking support to the new class.
156     */
157    private final void addLockSupport()
158    {
159        //---* Add the locks *-------------------------------------------------
160        var field = getComposer().fieldBuilder( AutoLock.class, STD_FIELD_ReadLock.toString(), PRIVATE, FINAL )
161            .addJavadoc(
162                """
163                The "read" lock.
164                """ )
165            .build();
166        addField( STD_FIELD_ReadLock, field );
167
168        field = getComposer().fieldBuilder( AutoLock.class, STD_FIELD_WriteLock.toString(), PRIVATE, FINAL )
169            .addJavadoc(
170                """
171                The "write" lock.
172                """ )
173            .build();
174        addField( STD_FIELD_WriteLock, field );
175
176        //---* Initialise the locks *------------------------------------------
177        final var code = getComposer().codeBlockBuilder()
178            .add(
179                """
180                
181                //---* Create the locks and initialise them *--------------------------
182                """ )
183            .addStatement( "final var lock = new $T()", ReentrantReadWriteLock.class )
184            .addStatement( "$N = $T.of( lock.readLock() )", getField( STD_FIELD_ReadLock ), AutoLock.class )
185            .addStatement( "$N = $T.of( lock.writeLock() )", getField( STD_FIELD_WriteLock ), AutoLock.class )
186            .build();
187        addConstructorCode( code );
188    }   //  addLockSupport()
189
190    /**
191     *  Adds "unchecked" to the
192     *  {@link SuppressWarnings}
193     *  annotation for the constructor if the given type is a
194     *  {@link java.util.List},
195     *  {@link java.util.Set},
196     *  {@link java.util.Map}
197     *  or an otherwise parameterised type.
198     *
199     *  @param  typeName    The type to check.
200     *
201     *  @see ParameterizedTypeName
202     */
203    private final void addUnchecked( final TypeName typeName )
204    {
205        if( typeName instanceof ParameterizedTypeName ) addConstructorSuppressedWarning( UNCHECKED );
206    }   //  addUnchecked()
207
208    /**
209     *  {@inheritDoc}
210     */
211    @Override
212    public final void build()
213    {
214        //---* Generate the default stuff *------------------------------------
215        addListenerSupport();
216        if( isSynchronized() ) addLockSupport();
217
218        //---* Generate the properties *---------------------------------------
219        for( final var iterator = getProperties(); iterator.hasNext(); )
220        {
221            generateProperty( iterator.next() );
222        }
223
224        //---* Create the initialisation code in the constructor *-------------
225        getConfiguration().getInitDataMethod()
226            .ifPresent( this::composeInitializationCodeFromMethod );
227        getConfiguration().getInitDataResource()
228            .ifPresent( this::composeInitializationCodeFromResource );
229
230        //---* Create 'toString()' *-------------------------------------------
231        createToString();
232    }   //  build()
233
234    /**
235     *  Composes the constructor code that initialises the properties from the
236     *  result of a call to the {@code initData()} method.
237     *
238     *  @param  method  The {@code initData()} method.
239     */
240    private final void composeInitializationCodeFromMethod( final MethodSpec method )
241    {
242        final var builder = getComposer().codeBlockBuilder()
243            .add(
244                """
245                
246                /*
247                 * Initialise the properties from '$1N()'.
248                 */
249                """, method
250            )
251            .beginControlFlow(
252                """
253                try
254                """
255            );
256
257        if( method.hasModifier( STATIC ) )
258        {
259            builder.addStatement( "final var initData = $1T.initData()", getConfiguration().getSpecification() );
260        }
261        else
262        {
263            builder.addStatement( "final var initData = initData()" );
264        }
265
266        builder.beginControlFlow(
267            """
268            if( isNull( initData ) )
269            """ )
270            .addStaticImport( Objects.class, "isNull" )
271            .addStatement(
272                """
273                throw new $T( "initData() returns null" )""", ValidationException.class )
274            .endControlFlow();
275
276        PropertyLoop:
277        for( final var iterator = getProperties(); iterator.hasNext(); )
278        {
279            final var propertySpec = iterator.next().merge();
280            if( propertySpec.hasFlag( GETTER_IS_DEFAULT ) ) continue PropertyLoop;
281            if( propertySpec.hasFlag( PROPERTY_IS_SPECIAL )
282                && propertySpec.getSpecialPropertyType().filter( spt -> spt == CONFIG_PROPERTY_RESOURCEBUNDLE ).isEmpty() )
283            {
284                continue PropertyLoop;
285            }
286            if( propertySpec.hasFlag( SYSTEM_PROPERTY ) ) continue PropertyLoop;
287            if( propertySpec.hasFlag( ENVIRONMENT_VARIABLE ) ) continue PropertyLoop;
288            if( propertySpec.hasFlag( SYSTEM_PREFERENCE ) ) continue PropertyLoop;
289
290            final var propertyType = propertySpec.getPropertyType();
291            final var field = propertySpec.getFieldName();
292            final var propertyName = propertySpec.getPropertyName();
293            addUnchecked( propertyType );
294
295            builder.beginControlFlow(
296                """
297                if( initData.containsKey( $1S ) )
298                """, propertyName )
299                .addStatement( "$1N = ($2T) initData.get( $3S )", field, propertyType.box(), propertyName )
300                .endControlFlow();
301        }   //  PropertyLoop:
302
303        builder.nextControlFlow(
304            """
305
306            catch( final $1T t )
307            """, Throwable.class )
308            .addStatement(
309                """
310                final var eiie = new $1T( "initData() failed" )""", ExceptionInInitializerError.class )
311            .addStatement( "eiie.addSuppressed( t )" )
312            .addStatement( "throw eiie" )
313            .endControlFlow();
314
315        //---* Add the code to the constructor *-------------------------------
316        addConstructorCode( builder.build() );
317    }   //  composeInitializationCodeFromMethod()
318
319    /**
320     *  Composes the constructor code that initialises the properties from the
321     *  provided resource.
322     *
323     *  @param  resourceName    The name of the resource.
324     */
325    @SuppressWarnings( "OverlyComplexMethod" )
326    private final void composeInitializationCodeFromResource( final String resourceName )
327    {
328        //---* The code that loads the properties from the resource *----------
329        final var builder = getComposer().codeBlockBuilder()
330            .add(
331                """
332
333                /*
334                 * Load initialisation data from resource "$1L".
335                 */
336                """, resourceName )
337            .beginControlFlow( EMPTY_STRING )
338            .addStatement( "final var resource = $1T.class.getResource( $2S )", getConfiguration().getSpecification(), resourceName )
339            .beginControlFlow(
340                """
341                if( isNull( resource ) )
342                """ )
343            .addStaticImport( Objects.class, "isNull" )
344            .addStatement(
345                """
346                final var fnfe = new $1T( "Resource '$2L'" )""", FileNotFoundException.class, resourceName )
347            .addStatement( """
348                final var eiie = new $1T( "Cannot find resource '$2L'" )""", ExceptionInInitializerError.class, resourceName )
349            .addStatement( "eiie.addSuppressed( fnfe )" )
350            .addStatement( "throw eiie" )
351            .endControlFlow()
352            .add( "\n" )
353            .addStatement( "final var initData = new $1T()", Properties.class )
354            .beginControlFlow(
355                """
356                try( final var inputStream = resource.openStream() )
357                """ )
358            .addStatement( "initData.load( inputStream )" )
359            .nextControlFlow(
360                """
361
362                catch( final $1T e )
363                """, IOException.class )
364            .addStatement(
365                """
366                final var eiie = new $1T( "Cannot load resource '%s'".formatted( resource.toExternalForm() ) )""", ExceptionInInitializerError.class )
367            .addStatement( "eiie.addSuppressed( e )" )
368            .addStatement( "throw eiie" )
369            .endControlFlow()
370            .add(
371                """
372
373                /*
374                 * Initialise the properties.
375                 */
376                """ )
377            .addStatement( "$1T value", String.class );
378
379        PropertyLoop:
380        for( final var iterator = getProperties(); iterator.hasNext(); )
381        {
382            final var propertySpec = iterator.next().merge();
383            if( propertySpec.hasFlag( GETTER_IS_DEFAULT ) ) continue PropertyLoop;
384            if( propertySpec.hasFlag( PROPERTY_IS_SPECIAL ) ) continue PropertyLoop;
385            if( propertySpec.hasFlag( SYSTEM_PROPERTY ) ) continue PropertyLoop;
386            if( propertySpec.hasFlag( ENVIRONMENT_VARIABLE ) ) continue PropertyLoop;
387            if( propertySpec.hasFlag( SYSTEM_PREFERENCE ) ) continue PropertyLoop;
388
389            /*
390             * Without a StringConverter we cannot initialise the property from
391             * the resource.
392             */
393            if( propertySpec.getStringConverterClass().isEmpty() ) continue PropertyLoop;
394            final var stringConverter = propertySpec.getStringConverterClass().get();
395
396            final var field = propertySpec.getFieldName();
397            final var propertyName = propertySpec.getPropertyName();
398
399            builder.add( "\n" )
400                .addStatement( "value = initData.getProperty( $1S )", propertyName )
401                .beginControlFlow(
402                    """
403                    if( nonNull( value ) )
404                    """ )
405                .addStaticImport( Objects.class, "nonNull" );
406            switch( determineStringConverterInstantiation( stringConverter, propertySpec.isEnum() ) )
407            {
408                case BY_INSTANCE -> builder.addStatement( "final var stringConverter = $1T.INSTANCE", stringConverter );
409                case THROUGH_CONSTRUCTOR -> builder.addStatement( "final var stringConverter = new $1T()", stringConverter );
410                case AS_ENUM -> builder.addStatement( "final var stringConverter = new $1T( $2T.class )", stringConverter, propertySpec.getPropertyType() );
411            }
412            builder.addStatement( "$N = stringConverter.fromString( value )", field )
413                .endControlFlow();
414        }   //  PropertyLoop:
415        builder.endControlFlow();
416
417        addConstructorSuppressedWarning( THROW_CAUGHT_LOCALLY );
418
419        //---* Add the code block *--------------------------------------------
420        addConstructorCode( builder.build() );
421    }   //  composeInitializationCodeFromResource()
422
423    /**
424     *  <p>{@summary Creates the implementation of the method
425     *  {@link Object#toString()}
426     *  for the configuration bean.} The output of that method will be like
427     *  this:</p>
428     *  <pre><code>&lt;<i>ClassName</i>&gt; <b>[</b>&lt;<i>PropertyName</i>&gt; <b>= &quot;</b>&lt;<i>PropertyValue</i>&gt;<b>&quot;</b>[<b>,</b> …]<b>]</b></code></pre>
429     */
430    @SuppressWarnings( "OverlyComplexMethod" )
431    private final void createToString()
432    {
433        //---* Create the 'toString()' method *--------------------------------
434        final var builder = getComposer().createToStringBuilder()
435            .addStatement( "final var prefix = format ( $1S, getClass().getName() )", "%s [" )
436            .addStaticImport( String.class, "format" )
437            .addStatement( "final var joiner = new $1T( $2S, prefix, $3S )", StringJoiner.class, ", ", "]" )
438            .addCode( "\n" );
439
440        //---* Add the locking *-----------------------------------------------
441        final var lock = getConfiguration().getSynchronizationRequired()
442             ? getField( STD_FIELD_ReadLock )
443             : null;
444        if( nonNull( lock) ) builder.beginControlFlow(
445            """
446            try( final var ignored = $N.lock() )
447            """, lock );
448        final var commentLen = nonNull( lock ) ? 67 : 71;
449
450        //---* Add the code *--------------------------------------------------
451        var addEmptyLine = false;
452        PropertyLoop:
453        for( final var iterator = getProperties(); iterator.hasNext(); )
454        {
455            final var propertySpec = iterator.next().merge();
456            if( propertySpec.hasFlag( EXEMPT_FROM_TOSTRING ) ) continue PropertyLoop;
457
458            if( addEmptyLine ) builder.addCode( "\n" );
459            addEmptyLine = true;
460            final var propertyName = propertySpec.getPropertyName();
461            final var comment = "//---* Property \"%1$s\" *%2$s".formatted( propertyName, repeat( '-', 80 ) )
462                .substring( 0, commentLen );
463            builder.addCode(
464                """
465                $L
466                """, comment )
467                .beginControlFlow( EMPTY_STRING );
468
469            if( !propertySpec.hasFlag( GETTER_IS_DEFAULT ) )
470            {
471                //---* We have a field … *-------------------------------------
472                final var field = propertySpec.getFieldName();
473
474                if( propertySpec.getStringConverterClass().isPresent() )
475                {
476                    final var stringConverter = propertySpec.getStringConverterClass().get();
477                    switch( determineStringConverterInstantiation( stringConverter, propertySpec.isEnum() ) )
478                    {
479                        case BY_INSTANCE -> builder.addStatement( "final var stringConverter = $1T.INSTANCE", stringConverter );
480                        case THROUGH_CONSTRUCTOR -> builder.addStatement( "final var stringConverter = new $1T()", stringConverter );
481                        case AS_ENUM -> builder.addStatement( "final var stringConverter = new $1T( $2T.class )", stringConverter, propertySpec.getPropertyType() );
482                    }
483                    builder.addStatement( "final var value = stringConverter.toString( $1L )", field )
484                        .addStatement(
485                            """
486                            joiner.add( format( "$1N = \\"%1$$s\\"", nonNull( value ) ? value : NULL_STRING ) )\
487                            """, propertyName )
488                        .addStaticImport( Objects.class, "nonNull" )
489                        .addStaticImport( CommonConstants.class, "NULL_STRING" )
490                        .addStaticImport( String.class, "format" );
491                }
492                else
493                {
494                    builder.addStatement(
495                        """
496                        joiner.add( format( "$1N = \\"%1$$S\\"", $2T.toString( $3L ) ) )\
497                        """, propertyName, Objects.class, field )
498                        .addStaticImport( String.class, "format" );
499                }
500            }
501            else if( propertySpec.getGetterMethodName().isPresent())
502            {
503                //---* We just have a getter … *-------------------------------
504                final var getterMethod = propertySpec.getGetterMethodName().get();
505                if( propertySpec.getStringConverterClass().isPresent() )
506                {
507                    final var stringConverter = propertySpec.getStringConverterClass().get();
508                    switch( determineStringConverterInstantiation( stringConverter, propertySpec.isEnum() ) )
509                    {
510                        case BY_INSTANCE -> builder.addStatement( "final var stringConverter = $1T.INSTANCE", stringConverter );
511                        case THROUGH_CONSTRUCTOR -> builder.addStatement( "final var stringConverter = new $1T()", stringConverter );
512                        case AS_ENUM -> builder.addStatement( "final var stringConverter = new $1T( $2T.class )", stringConverter, propertySpec.getPropertyType() );
513                    }
514                    builder.addStatement( "final var value = stringConverter.toString( $1L() )", getterMethod )
515                        .addStatement(
516                            """
517                            joiner.add( format( "$1N = \\"%1$$s\\"", nonNull( value ) ? value : NULL_STRING ) )\
518                            """, propertyName )
519                        .addStaticImport( Objects.class, "nonNull" )
520                        .addStaticImport( CommonConstants.class, "NULL_STRING" )
521                        .addStaticImport( String.class, "format" );
522                }
523                else
524                {
525                    builder.addStatement(
526                        """
527                        joiner.add( format( "$1N = \\"%1$$s\\"", $2T.toString( $3L() ) ) )\
528                        """, propertyName, Objects.class, getterMethod )
529                        .addStaticImport( String.class, "format" );
530                }
531            }
532            builder.endControlFlow();
533        }   //  PropertiesLoop:
534
535        //---* Cleanup *-------------------------------------------------------
536        if( nonNull( lock) ) builder.endControlFlow();
537
538        builder.addCode(
539            """
540            
541            //---* Create the return value *---------------------------------------
542            """
543            )
544            .addStatement( "final var retValue = joiner.toString()" )
545            .addCode( getComposer().createReturnStatement() );
546
547        addMethod( STD_METHOD_ToString, builder.build() );
548    }   //  createToString()
549
550    /**
551     *  Generates the methods, fields and other code for the given property.
552     *
553     *  @param  rawProperty    The property specification.
554     */
555    private final void generateProperty( final PropertySpec rawProperty )
556    {
557        final var property = rawProperty.merge();
558
559        //---* Create the field *----------------------------------------------
560        property.createField( this ).ifPresent( this::addField );
561
562        /*
563         * Create the constructor code for the initialisation of the property.
564         */
565        property.createConstructorFragment( this ).ifPresent( this::addConstructorCode );
566
567        //---* Create the getter *---------------------------------------------
568        if( property.getPropertyName().equals( CONFIG_PROPERTY_RESOURCEBUNDLE.getPropertyName() ) )
569        {
570            property.createGetter( this ).ifPresent( p -> addMethod( STD_METHOD_GetRessourceBundle, p ) );
571        }
572        else
573        {
574            property.createGetter( this ).ifPresent( this::addMethod );
575        }
576
577        //---* Create the getter *---------------------------------------------
578        property.createSetter( this ).ifPresent( this::addMethod );
579
580        //---* Create the 'add' method *---------------------------------------
581        property.createAddMethod( this ).ifPresent( this::addMethod );
582    }   //  generateProperty()
583}
584//  class ConfigBeanBuilder
585
586/*
587 *  End of File
588 */