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 */