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;
019
020import static java.lang.String.format;
021import static java.util.Collections.unmodifiableMap;
022import static javax.lang.model.element.Modifier.FINAL;
023import static javax.lang.model.element.Modifier.PUBLIC;
024import static org.apiguardian.api.API.Status.STABLE;
025import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_CHARSET;
026import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_CLOCK;
027import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_LOCALE;
028import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_MESSAGEPREFIX;
029import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_PID;
030import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_RANDOM;
031import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_RESOURCEBUNDLE;
032import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_SESSION;
033import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_TIMEZONE;
034import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_MissingInterface;
035import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_MUTABLE;
036import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.OVERLY_COMPLEX_CLASS;
037import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.OVERLY_COUPLED_CLASS;
038import static org.tquadrat.foundation.lang.Objects.nonNull;
039import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
040
041import java.util.EnumMap;
042import java.util.EnumSet;
043import java.util.Map;
044import java.util.Set;
045
046import org.apiguardian.api.API;
047import org.tquadrat.foundation.annotation.ClassVersion;
048import org.tquadrat.foundation.ap.CodeGenerationError;
049import org.tquadrat.foundation.config.CLIBeanSpec;
050import org.tquadrat.foundation.config.ConfigBeanSpec;
051import org.tquadrat.foundation.config.I18nSupport;
052import org.tquadrat.foundation.config.INIBeanSpec;
053import org.tquadrat.foundation.config.PreferencesBeanSpec;
054import org.tquadrat.foundation.config.SessionBeanSpec;
055import org.tquadrat.foundation.config.SpecialPropertyType;
056import org.tquadrat.foundation.config.ap.CodeGenerationConfiguration;
057import org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor;
058import org.tquadrat.foundation.config.ap.impl.codebuilders.CLIBeanBuilder;
059import org.tquadrat.foundation.config.ap.impl.codebuilders.CodeGeneratorContext;
060import org.tquadrat.foundation.config.ap.impl.codebuilders.ConfigBeanBuilder;
061import org.tquadrat.foundation.config.ap.impl.codebuilders.I18nSupportBuilder;
062import org.tquadrat.foundation.config.ap.impl.codebuilders.INIBeanBuilder;
063import org.tquadrat.foundation.config.ap.impl.codebuilders.MapImplementor;
064import org.tquadrat.foundation.config.ap.impl.codebuilders.PreferencesBeanBuilder;
065import org.tquadrat.foundation.config.ap.impl.codebuilders.SessionBeanBuilder;
066import org.tquadrat.foundation.config.ap.impl.specialprops.CharsetProperty;
067import org.tquadrat.foundation.config.ap.impl.specialprops.ClockProperty;
068import org.tquadrat.foundation.config.ap.impl.specialprops.LocaleProperty;
069import org.tquadrat.foundation.config.ap.impl.specialprops.MessagePrefixProperty;
070import org.tquadrat.foundation.config.ap.impl.specialprops.ProcessIdProperty;
071import org.tquadrat.foundation.config.ap.impl.specialprops.RandomProperty;
072import org.tquadrat.foundation.config.ap.impl.specialprops.ResourceBundleProperty;
073import org.tquadrat.foundation.config.ap.impl.specialprops.SessionKeyProperty;
074import org.tquadrat.foundation.config.ap.impl.specialprops.TimeZoneProperty;
075import org.tquadrat.foundation.javacomposer.CodeBlock;
076import org.tquadrat.foundation.javacomposer.JavaComposer;
077import org.tquadrat.foundation.javacomposer.JavaFile;
078import org.tquadrat.foundation.javacomposer.MethodSpec;
079import org.tquadrat.foundation.javacomposer.SuppressableWarnings;
080import org.tquadrat.foundation.javacomposer.TypeSpec;
081
082/**
083 *  Generates the code for the new configuration bean.
084 *
085 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
086 *  @version $Id: CodeGenerator.java 1061 2023-09-25 16:32:43Z tquadrat $
087 *  @UMLGraph.link
088 *  @since 0.1.0
089 */
090@SuppressWarnings( "OverlyCoupledClass" )
091@ClassVersion( sourceVersion = "$Id: CodeGenerator.java 1061 2023-09-25 16:32:43Z tquadrat $" )
092@API( status = STABLE, since = "0.1.0" )
093public final class CodeGenerator implements CodeGeneratorContext
094{
095        /*------------*\
096    ====** Attributes **=======================================================
097        \*------------*/
098    /**
099     *  The class builder.
100     */
101    private final TypeSpec.Builder m_ClassBuilder;
102
103    /**
104     *  The instance of
105     *  {@link org.tquadrat.foundation.javacomposer.JavaComposer}
106     *  that is used for the code generation.
107     */
108    @SuppressWarnings( "UseOfConcreteClass" )
109    private final JavaComposer m_Composer;
110
111    /**
112     *  The configuration for the generation process.
113     */
114    @SuppressWarnings( "UseOfConcreteClass" )
115    private final CodeGenerationConfiguration m_Configuration;
116
117    /**
118     *  The builder for the constructor.
119     */
120    private final MethodSpec.Builder m_Constructor;
121
122    /**
123     *  The builder for body of the constructor.
124     */
125    private final CodeBlock.Builder m_ConstructorCode;
126
127    /**
128     *  The list of the suppressed warnings for the constructor of the new
129     *  configuration bean.
130     */
131    private final Set<SuppressableWarnings> m_SuppressedWarningsForConstructor = EnumSet.noneOf( SuppressableWarnings.class );
132
133        /*------------------------*\
134    ====** Static Initialisations **===========================================
135        \*------------------------*/
136    /**
137     *  The special properties.
138     */
139    @SuppressWarnings( "StaticCollection" )
140    private static final Map<SpecialPropertyType,SpecialPropertySpec> m_SpecialProperties;
141
142    static
143    {
144        final Map<SpecialPropertyType,SpecialPropertySpec> map = new EnumMap<>( SpecialPropertyType.class );
145        map.put( CONFIG_PROPERTY_CHARSET, new CharsetProperty() );
146        map.put( CONFIG_PROPERTY_CLOCK, new ClockProperty() );
147        map.put( CONFIG_PROPERTY_LOCALE, new LocaleProperty() );
148        map.put( CONFIG_PROPERTY_MESSAGEPREFIX, new MessagePrefixProperty() );
149        map.put( CONFIG_PROPERTY_PID, new ProcessIdProperty() );
150        map.put( CONFIG_PROPERTY_RANDOM, new RandomProperty() );
151        map.put( CONFIG_PROPERTY_RESOURCEBUNDLE, new ResourceBundleProperty() );
152        map.put( CONFIG_PROPERTY_SESSION, new SessionKeyProperty() );
153        map.put( CONFIG_PROPERTY_TIMEZONE, new TimeZoneProperty() );
154        m_SpecialProperties = unmodifiableMap( map );
155    }
156
157        /*--------------*\
158    ====** Constructors **=====================================================
159        \*--------------*/
160    /**
161     *  Creates a new instance of {@code CodeGenerator}.
162     *
163     *  @param  configuration   The configuration for the generation process.
164     */
165    @SuppressWarnings( "UseOfConcreteClass" )
166    public CodeGenerator( final CodeGenerationConfiguration configuration )
167    {
168        m_Configuration = requireNonNullArgument( configuration, "configuration" );
169        m_Composer = m_Configuration.getComposer();
170
171        m_ClassBuilder = m_Composer.classBuilder( m_Configuration.getClassName() )
172            .addSuppressableWarning( OVERLY_COUPLED_CLASS )
173            .addSuppressableWarning( OVERLY_COMPLEX_CLASS );
174
175        m_Constructor = m_Composer.constructorBuilder();
176        m_ConstructorCode = m_Composer.codeBlockBuilder();
177    }   //  CodeGenerator()
178
179        /*---------*\
180    ====** Methods **==========================================================
181        \*---------*/
182    /**
183     *  {@inheritDoc}
184     */
185    @Override
186    public final void addConstructorSuppressedWarning( final SuppressableWarnings warning )
187    {
188        if( nonNull( warning ) ) m_SuppressedWarningsForConstructor.add( warning );
189    }   //  addConstructorSuppressedWarning()
190
191    /**
192     *  Generates the code from the configuration provided in the constructor.
193     *
194     *  @return The generated code.
195     */
196    @SuppressWarnings( {"PublicMethodNotExposedInInterface", "OverlyCoupledMethod"} )
197    public final JavaFile createCode()
198    {
199        /*
200         * If the configuration bean specification implements I18nSupport, no
201         * setter for the resource bundle is allowed, but the getter modifies
202         * the attribute for the resource bundle if the locale changes;
203         * therefore we need to ensure that this attribute is not final.
204         */
205        if( m_Configuration.implementInterface( I18nSupport.class ) )
206        {
207           m_Configuration.getProperty( CONFIG_PROPERTY_RESOURCEBUNDLE.getPropertyName() ).ifPresent( p -> ((PropertySpecImpl) p).setFlag( PROPERTY_IS_MUTABLE )  );
208        }
209
210        /*
211         *  The interface ConfigBeanSpec is mandatory; if this is not extended
212         *  by the current configuration bean specification, we are done …
213         */
214        if( !m_Configuration.implementInterface( ConfigBeanSpec.class ) )
215        {
216            throw new CodeGenerationError( format( MSG_MissingInterface, m_Configuration.getSpecification().canonicalName(), ConfigBeanSpec.class.getName() ) );
217        }
218        new ConfigBeanBuilder( this ).build();
219
220        /*
221         *  The configuration bean specification can extend the interface
222         *  I18nSupport.
223         */
224        if( m_Configuration.implementInterface( I18nSupport.class ) )
225        {
226            new I18nSupportBuilder( this ).build();
227        }
228
229        /*
230         *  The configuration bean specification can extend the interface
231         *  SessionBeanSpec.
232         */
233        if( m_Configuration.implementInterface( SessionBeanSpec.class ) )
234        {
235            new SessionBeanBuilder( this ).build();
236        }
237
238        /*
239         *  The configuration bean specification can extend the interface
240         *  Map<String,Object>.
241         */
242        if( m_Configuration.implementInterface( Map.class ) )
243        {
244            new MapImplementor( this ).build();
245        }
246
247        /*
248         *  The configuration bean specification can extend the interface
249         *  CLIBeanSpec.
250         */
251        if( m_Configuration.implementInterface( CLIBeanSpec.class ) )
252        {
253            new CLIBeanBuilder( this ).build();
254        }
255
256        /*
257         *  The configuration bean specification extends the interface
258         *  PreferencesBeanSpec.
259         */
260        if( m_Configuration.implementInterface( PreferencesBeanSpec.class ) )
261        {
262            new PreferencesBeanBuilder( this ).build();
263        }
264
265        /*
266         *  The configuration bean specification extends the interface
267         *  INIBeanSpec.
268         */
269        if( m_Configuration.implementInterface( INIBeanSpec.class ) )
270        {
271            new INIBeanBuilder( this ).build();
272        }
273
274        //---* Build the constructor *-----------------------------------------
275        var constructorBody = m_ConstructorCode.build();
276        if( constructorBody.isEmpty() )
277        {
278            constructorBody = m_Composer.codeBlockOf(
279                """
280                /* Just exists */
281                """ );
282        }
283
284        //---* Create the @SuppressWarnings annotation *-----------------------
285        m_Composer.createSuppressWarningsAnnotation( m_SuppressedWarningsForConstructor )
286            .ifPresent( m_Constructor::addAnnotation );
287
288        //---* Finish the constructor *----------------------------------------
289        final var constructor = m_Constructor
290            .addJavadoc(
291                """
292                Creates a new {@code $L} instance.
293                """, m_Configuration.getClassName() )
294            .addModifiers( PUBLIC )
295            .addCode( constructorBody )
296            .build();
297
298        //---* Finish the class *----------------------------------------------
299        final var sourceVersion = "Generated through %1$s at %2$s".formatted( ConfigAnnotationProcessor.class.getName(), m_Configuration.getBuildTime().toString() );
300        m_Configuration.getBaseClass().ifPresent( m_ClassBuilder::superclass );
301        final var configurationBean = m_ClassBuilder.addSuperinterface( m_Configuration.getSpecification() )
302            .addModifiers( PUBLIC, FINAL )
303            .addAnnotation( m_Composer.createClassVersionAnnotation( sourceVersion ) )
304            .addJavadoc(
305                """
306                The configuration bean that implements
307                {@link $T}.
308                """,
309                m_Configuration.getSpecification() )
310            .addMethod( constructor )
311            .build();
312
313        //---* Create the return value *---------------------------------------
314        final var retValue = m_Composer.javaFileBuilder( m_Configuration.getPackageName(), configurationBean )
315            .addFileComment(
316                """
317                ============================================================================
318                This file inherits the copyright and license(s) from the interface that is
319                implemented by the class
320                
321                    $1L.$2N
322                  
323                Refer to
324                
325                    $3L
326                    
327                and the file comment there for the details.
328                ============================================================================""",
329                m_Configuration.getPackageName(), configurationBean, m_Configuration.getSpecification().canonicalName()
330            )
331            .build();
332
333        //---* Done *----------------------------------------------------------
334        return retValue;
335    }   //  createCode()
336
337    /**
338     * {@inheritDoc}
339     */
340    @Override
341    public final TypeSpec.Builder getClassBuilder() { return m_ClassBuilder; }
342
343    /**
344     * {@inheritDoc}
345     */
346    @Override
347    public final JavaComposer getComposer() { return m_Composer; }
348
349    /**
350     * {@inheritDoc}
351     */
352    @Override
353    public final CodeGenerationConfiguration getConfiguration() { return m_Configuration; }
354
355    /**
356     * {@inheritDoc}
357     */
358    @Override
359    public final MethodSpec.Builder getConstructorBuilder() { return m_Constructor; }
360
361    /**
362     * {@inheritDoc}
363     */
364    @Override
365    public final CodeBlock.Builder getConstructorCodeBuilder() { return m_ConstructorCode; }
366
367    /**
368     *  Returns the definition for the special property type.
369     *
370     *  @param  type    The special property type.
371     *  @return The special property specification.
372     */
373    public static final SpecialPropertySpec getSpecialPropertySpecification( final SpecialPropertyType type )
374    {
375        final var retValue = m_SpecialProperties.get( requireNonNullArgument( type, "type" ) );
376
377        //---* Done *----------------------------------------------------------
378        return retValue;
379    }   //  getSpecialPropertySpecification
380}
381//  class CodeGenerator
382
383/*
384 *  End of File
385 */