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; 019 020import static java.lang.String.format; 021import static java.util.Collections.unmodifiableMap; 022import static org.apiguardian.api.API.Status.MAINTAINED; 023import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_DuplicateProperty; 024import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_IllegalImplementation; 025import static org.tquadrat.foundation.lang.Objects.isNull; 026import static org.tquadrat.foundation.lang.Objects.nonNull; 027import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 028import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 029import static org.tquadrat.foundation.util.Comparators.caseInsensitiveComparator; 030import static org.tquadrat.foundation.util.StringUtils.isEmptyOrBlank; 031import static org.tquadrat.foundation.util.StringUtils.isNotEmptyOrBlank; 032 033import javax.lang.model.element.Name; 034import javax.lang.model.util.Elements; 035import java.lang.reflect.Type; 036import java.time.Instant; 037import java.util.Collection; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.List; 041import java.util.Map; 042import java.util.Optional; 043import java.util.SortedMap; 044import java.util.TreeMap; 045 046import org.apiguardian.api.API; 047import org.tquadrat.foundation.annotation.ClassVersion; 048import org.tquadrat.foundation.ap.APHelper; 049import org.tquadrat.foundation.ap.CodeGenerationError; 050import org.tquadrat.foundation.config.INIGroup; 051import org.tquadrat.foundation.config.ap.impl.PropertySpecImpl; 052import org.tquadrat.foundation.javacomposer.ClassName; 053import org.tquadrat.foundation.javacomposer.JavaComposer; 054import org.tquadrat.foundation.javacomposer.MethodSpec; 055import org.tquadrat.foundation.javacomposer.TypeName; 056 057/** 058 * An instance of this class provides the configuration for the code 059 * generation, and it collects the results from the different code generators. 060 * 061 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 062 * @version $Id: CodeGenerationConfiguration.java 1105 2024-02-28 12:58:46Z tquadrat $ 063 * @UMLGraph.link 064 * @since 0.1.0 065 */ 066@SuppressWarnings( {"ClassWithTooManyFields", "ClassWithTooManyMethods"} ) 067@ClassVersion( sourceVersion = "$Id: CodeGenerationConfiguration.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 068@API( status = MAINTAINED, since = "0.1.0" ) 069public final class CodeGenerationConfiguration 070{ 071 /*------------*\ 072 ====** Attributes **======================================================= 073 \*------------*/ 074 /** 075 * The base bundle name for the resource bundle if i18n support is 076 * required. 077 * 078 * @note This is <i>not</i> the value but the fully qualified field name 079 * where to find that value! 080 */ 081 private String m_BaseBundleName = null; 082 083 /** 084 * The optional base class for the new configuration bean. 085 */ 086 private final TypeName m_BaseClass; 087 088 /** 089 * The build time; this is provided by the configuration as this allows 090 * easier testing. 091 */ 092 private final Instant m_BuildTime = Instant.now(); 093 094 /** 095 * The class name for the configuration bean class, without the package. 096 */ 097 private final Name m_ClassName; 098 099 /** 100 * The 101 * {@link JavaComposer} 102 * instance that is used for the code generation. 103 */ 104 @SuppressWarnings( "UseOfConcreteClass" ) 105 private final JavaComposer m_Composer; 106 107 /** 108 * The processing environment. 109 */ 110 private final APHelper m_Environment; 111 112 /** 113 * The comment for the configuration file. 114 */ 115 private String m_INIFileComment = null; 116 117 /** 118 * The flag that indicates whether the configuration must exist prior to 119 * the first open attempt. 120 */ 121 private boolean m_INIFileMustExist; 122 123 /** 124 * The name for the configuration file. 125 */ 126 private String m_INIFilePath = null; 127 128 /** 129 * The {@code INI} file groups. The key is the name of the group, the 130 * value is the respective comment for the group. 131 */ 132 private final Map<String,String> m_INIGroups = new TreeMap<>(); 133 134 /** 135 * The method that is provided as a source for the initialisation of the 136 * properties of the configuration bean. 137 */ 138 private MethodSpec m_InitDataMethod = null; 139 140 /** 141 * The name of the resource that is used to initialise the properties of 142 * the configuration bean. 143 */ 144 private String m_InitDataResource = null; 145 146 /** 147 * The interfaces that are extended by the configuration bean 148 * specification. 149 */ 150 private final Collection<TypeName> m_InterfacesToImplement = new HashSet<>(); 151 152 /** 153 * The message prefix for the i18n support. 154 * 155 * @note This is <i>not</i> the value but the fully qualified field name 156 * where to find that value! 157 */ 158 private String m_MessagePrefix = null; 159 160 /** 161 * The name of the package for the configuration bean. 162 */ 163 private final Name m_PackageName; 164 165 /** 166 * The change listener class for the {@code Preferences}. 167 */ 168 private TypeName m_PreferenceChangeListenerClass; 169 170 /** 171 * <p>{@summary The properties for the configuration bean.} The name of 172 * the property is the key.</p> 173 */ 174 private final SortedMap<String, PropertySpecImpl> m_Properties = new TreeMap<>( caseInsensitiveComparator() ); 175 176 /** 177 * The configuration bean specification. 178 */ 179 private final ClassName m_Specification; 180 181 /** 182 * The name of the preferences root node. 183 */ 184 private String m_PreferencesRoot = null; 185 186 /** 187 * This flag indicates whether the access to the configuration bean 188 * properties must be thread-safe. 189 */ 190 private final boolean m_SynchronizeAccess; 191 192 /*--------------*\ 193 ====** Constructors **===================================================== 194 \*--------------*/ 195 /** 196 * Creates an instance of {@code CodeGenerationConfiguration}. 197 * 198 * @param environment The access to the processing environment. 199 * @param composer The 200 * {@link JavaComposer} 201 * instance that is used for the code generation. 202 * @param specification The configuration specification, the interface 203 * that defines the configuration bean. 204 * @param className The name for the configuration bean class, without 205 * the package name. 206 * @param packageName The name of the package for the configuration bean 207 * class. 208 * @param baseClass The optional base class for the new configuration 209 * bean class. 210 * @param synchronizeAccess {@code true} if the access to the 211 * configuration bean properties should be thread safe, {@code false} 212 * if a synchronisation/locking is not required. 213 */ 214 @SuppressWarnings( {"UseOfConcreteClass", "ConstructorWithTooManyParameters"} ) 215 public CodeGenerationConfiguration( final APHelper environment, final JavaComposer composer, final ClassName specification, final Name className, final Name packageName, final TypeName baseClass, final boolean synchronizeAccess ) 216 { 217 m_Environment = requireNonNullArgument( environment, "environment" ); 218 m_Composer = requireNonNullArgument( composer, "composer" ); 219 m_ClassName = requireNotEmptyArgument( className, "className" ); 220 m_PackageName = requireNonNullArgument( packageName, "packageName" ); 221 m_Specification = requireNonNullArgument( specification, "specification" ); 222 m_BaseClass = baseClass; 223 m_SynchronizeAccess = synchronizeAccess; 224 } // CodeGenerationConfiguration() 225 226 /*---------*\ 227 ====** Methods **========================================================== 228 \*---------*/ 229 /** 230 * Adds an {@code INI} file group. 231 * 232 * @param name The name for the group. 233 * @param comment The comment for the group. 234 */ 235 public final void addINIGroup( final String name, final String comment ) 236 { 237 if( isNotEmptyOrBlank( name ) ) 238 { 239 if( isEmptyOrBlank( comment ) ) 240 { 241 m_INIGroups.remove( name ); 242 } 243 else 244 { 245 m_INIGroups.put( name, comment ); 246 } 247 } 248 } // addINIGroup() 249 250 /** 251 * Adds an {@code INI} file group. 252 * 253 * @param group The group definition. 254 */ 255 public final void addINIGroup( final INIGroup group ) 256 { 257 addINIGroup( requireNonNullArgument( group, "group" ).name(), group.comment() ); 258 } // addINIGroup() 259 260 /** 261 * Adds some interfaces that have to be implemented by the new 262 * configuration beam. 263 * 264 * @param interfacesToImplement The classes for the interfaces to 265 * implement. 266 */ 267 public final void addInterfacesToImplement( final Collection<? extends TypeName> interfacesToImplement ) 268 { 269 m_InterfacesToImplement.addAll( requireNotEmptyArgument( interfacesToImplement, "interfacesToImplement" ) ); 270 } // addInterfaceToImplement() 271 272 /** 273 * <p>{@summary Adds a property to the new configuration bean.}</p> 274 * <p>If a property with the same name as the new one already exists, a 275 * {@link CodeGenerationError} 276 * will be thrown.</p> 277 * 278 * @param property The property 279 * @throws CodeGenerationError There is a duplicate property. 280 */ 281 public final void addProperty( final PropertySpec property ) throws CodeGenerationError 282 { 283 if( requireNonNullArgument( property, "property" ) instanceof final PropertySpecImpl propertyImpl ) 284 { 285 final var propertyName = propertyImpl.getPropertyName(); 286 if( nonNull( m_Properties.put( propertyName, propertyImpl ) ) ) 287 { 288 throw new CodeGenerationError( format( MSG_DuplicateProperty, propertyName ) ); 289 } 290 } 291 else 292 { 293 throw new CodeGenerationError( format( MSG_IllegalImplementation, PropertySpec.class.getName(), property.getClass().getName() ) ); 294 } 295 } // addProperty() 296 297 /** 298 * Returns the name of the field that holds the base bundle name for the 299 * resource bundle, in case i18n support is configured. 300 * 301 * @return An instance of 302 * {@link Optional} 303 * holding the fully qualified field name. 304 */ 305 public final Optional<String> getBaseBundleName() { return Optional.ofNullable( m_BaseBundleName ); } 306 307 /** 308 * Returns the base class for the new configuration bean. 309 * 310 * @return An instance of 311 * {@link Optional} 312 * that holds the base class. 313 */ 314 public final Optional<TypeName> getBaseClass() { return Optional.ofNullable( m_BaseClass ); } 315 316 /** 317 * Returns the build time for the new configuration bean. 318 * 319 * @return The build time. 320 */ 321 public final Instant getBuildTime() { return m_BuildTime; } 322 323 /** 324 * Returns the name of the class for the new configuration bean. 325 * 326 * @return The class name. 327 */ 328 public final Name getClassName() { return m_ClassName; } 329 330 /** 331 * Returns the 332 * {@link JavaComposer} 333 * instance that is used for the code generation. 334 * 335 * @return The composer instance. 336 */ 337 public final JavaComposer getComposer() { return m_Composer; } 338 339 /** 340 * Returns an implementation of some utility methods for operating on 341 * elements. 342 * 343 * @return The element utilities. 344 */ 345 public final Elements getElementUtils() { return getEnvironment().getElementUtils(); } 346 347 /** 348 * Returns the processing environment. 349 * 350 * @return The processing environment. 351 */ 352 public final APHelper getEnvironment() { return m_Environment; } 353 354 /** 355 * Returns the comment for the {@code INI} file. 356 * 357 * @return The comment for the configuration file. 358 */ 359 public final Optional<String> getINIFileComment() { return Optional.ofNullable( m_INIFileComment ); } 360 361 /** 362 * Returns the flag that indicates whether the configuration file must 363 * exist before the program starts. 364 * 365 * @return {@code true} if the file must exist already, {@code false} if 366 * it will be created on startup. 367 * 368 * @see org.tquadrat.foundation.config.INIFileConfig#mustExist() 369 */ 370 @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" ) 371 public final boolean getINIFileMustExist() { return m_INIFileMustExist; } 372 373 /** 374 * Returns the name for the file that backs the 375 * {@link org.tquadrat.foundation.inifile.INIFile} 376 * instance used by the generated configuration bean. 377 * 378 * @return An instance of 379 * {@link Optional} 380 * that holds the filename. 381 */ 382 public final Optional<String> getINIFilePath() { return Optional.ofNullable( m_INIFilePath ); } 383 384 /** 385 * Returns the {@code INI} file groups. 386 * 387 * @return The group names and the related comments. 388 */ 389 public final Map<String,String> getINIGroups() { return unmodifiableMap( m_INIGroups ); } 390 391 /** 392 * <p>{@summary Returns the method that is provided as a source for the 393 * initialisation of the properties of the configuration bean.}</p> 394 * <p>It it is {@code default} or {@code static}, the implementation of 395 * the method has to be part of the configuration bean specification 396 * interface itself, otherwise it has to be implemented in base class.</p> 397 * <p>It is an error if there is an {@code initData()} method that is 398 * neither {@code default} nor {@code static}, and no base class is 399 * defined with the 400 * {@link org.tquadrat.foundation.config.ConfigurationBeanSpecification @ConfigurationBeanSpecificatgion} 401 * annotation.</p> 402 * <p>The signature for the method has to be</p> 403 * <pre><code>public Map<String,Object> initData() throws Exception</code></pre> 404 * <p>and the return value is a map with the initialisation values, where 405 * the property names are the keys.</p> 406 * 407 * @return An instance of 408 * {@link Optional} 409 * that holds the method. 410 * 411 * @see org.tquadrat.foundation.config.ConfigurationBeanSpecification#baseClass() 412 */ 413 public final Optional<MethodSpec> getInitDataMethod() { return Optional.ofNullable( m_InitDataMethod ); } 414 415 /** 416 * Returns the name of the resource that is used to initialise the 417 * properties of the configuration bean. 418 * 419 * @return An instance of 420 * {@link Optional} 421 * that holds the resource name. 422 */ 423 public final Optional<String> getInitDataResource() { return Optional.ofNullable( m_InitDataResource ); } 424 425 /** 426 * Returns the interfaces that have to be implemented by the new 427 * configuration bean. 428 * 429 * @return The interfaces to implement. 430 */ 431 public final Collection<TypeName> getInterfacesToImplement() { return List.copyOf( m_InterfacesToImplement ); } 432 433 /** 434 * Returns the name of the field that holds the message prefix, in case 435 * i18n support is configured. 436 * 437 * @return An instance of 438 * {@link Optional} 439 * holding the fully qualified field name. 440 */ 441 public final Optional<String> getMessagePrefix() { return Optional.ofNullable( m_MessagePrefix ); } 442 443 /** 444 * Returns the name of the package for the new configuration bean. 445 * 446 * @return The package name. 447 */ 448 public final Name getPackageName() { return m_PackageName; } 449 450 /** 451 * <p>{@summary Returns the class for the {@code Preferences} change 452 * listener.} If no listener class is defined, the change listener support 453 * for the {@code Preferences} will be omitted. 454 * 455 * @return An instance of 456 * {@link Optional} 457 * that holds the listener class. 458 * 459 * @see org.tquadrat.foundation.config.PreferencesRoot#changeListenerClass() 460 */ 461 public final Optional<TypeName> getPreferenceChangeListenerClass() 462 { 463 final var retValue = Optional.ofNullable( m_PreferenceChangeListenerClass ); 464 465 //---* Done *---------------------------------------------------------- 466 return retValue; 467 } // getPreferenceChangeListenerClass() 468 469 /** 470 * Returns the name for the {@code Preferences} root node. 471 * 472 * @return The name for the {@code Preferences} root node. 473 * 474 * @see org.tquadrat.foundation.config.PreferencesRoot#nodeName() 475 */ 476 public final String getPreferencesRoot() 477 { 478 final var retValue = isNotEmptyOrBlank( m_PreferencesRoot) ? m_PreferencesRoot : m_Specification.canonicalName(); 479 480 //---* Done *---------------------------------------------------------- 481 return retValue; 482 } // getPreferencesRoot() 483 /** 484 * Returns the property with the given name. 485 * 486 * @param propertyName The name of the property. 487 * @return An instance of 488 * {@link Optional} 489 * that holds the retrieved property. 490 */ 491 public final Optional<PropertySpec> getProperty( final String propertyName ) 492 { 493 final Optional<PropertySpec> retValue = Optional.ofNullable( m_Properties.get( requireNotEmptyArgument( propertyName, "propertyName" ) ) ); 494 495 //---* Done *---------------------------------------------------------- 496 return retValue; 497 } // getProperty() 498 499 /** 500 * Returns the configuration bean specification. 501 * 502 * @return The configuration bean specification. 503 */ 504 public final ClassName getSpecification() { return m_Specification;} 505 506 /** 507 * Returns the flag that controls whether the generated code for the 508 * access to the configuration bean properties has to be thread-safe. 509 * 510 * @return {@code true} if synchronisation/locking is required, 511 * {@code false} if not. 512 */ 513 @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" ) 514 public final boolean getSynchronizationRequired() { return m_SynchronizeAccess; } 515 516 /** 517 * Checks whether a property with the given name does already exist. 518 * 519 * @param propertyName The name of the property. 520 * @return {@code true} if the property with the given name already 521 * exists, {@code false} otherwise. 522 */ 523 public final boolean hasProperty( final String propertyName ) { return m_Properties.containsKey( propertyName ); } 524 525 /** 526 * Checks whether the given interface must be implemented by the new 527 * configuration bean. 528 * 529 * @param interfaceToImplement The class for the interface that has to 530 * be implemented. 531 * @return {@code true} if the given interface must be implemented, 532 * {@code false} otherwise. 533 */ 534 @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" ) 535 public final boolean implementInterface( final TypeName interfaceToImplement ) 536 { 537 final var retValue = m_InterfacesToImplement.contains( requireNonNullArgument( interfaceToImplement, "interfaceToImplement" ) ); 538 539 //---* Done *---------------------------------------------------------- 540 return retValue; 541 } // implementInterface() 542 543 /** 544 * Checks whether the given interface must be implemented by the new 545 * configuration bean. 546 * 547 * @param interfaceToImplement The class for the interface that has to 548 * be implemented. 549 * @return {@code true} if the given interface must be implemented, 550 * {@code false} otherwise. 551 */ 552 @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" ) 553 public final boolean implementInterface( final Type interfaceToImplement ) 554 { 555 final var element = TypeName.from( interfaceToImplement ); 556 final var retValue = implementInterface( element ); 557 558 //---* Done *---------------------------------------------------------- 559 return retValue; 560 } // implementInterface() 561 562 /** 563 * Returns an 564 * {@link java.util.Iterator} 565 * over the defined properties. 566 * 567 * @return The iterator. 568 */ 569 public final Iterator<PropertySpec> propertyIterator() 570 { 571 final var iterator = m_Properties.values().iterator(); 572 @SuppressWarnings( {"AnonymousInnerClass"} ) 573 final Iterator<PropertySpec> retValue = new Iterator<>() 574 { 575 /** 576 * {@inheritDoc} 577 */ 578 @Override 579 public final boolean hasNext() { return iterator.hasNext(); } 580 581 /** 582 * {@inheritDoc} 583 */ 584 @Override 585 public final PropertySpec next() { return iterator.next(); } 586 }; 587 588 //---* Done *---------------------------------------------------------- 589 return retValue; 590 } // propertyIterator() 591 592 /** 593 * Sets the i18n parameters. 594 * 595 * @param messagePrefix The value for the message prefix. 596 * @param baseBundleName The name of the base bundle. 597 */ 598 public final void setI18NParameters( final String messagePrefix, final String baseBundleName ) 599 { 600 m_MessagePrefix = messagePrefix; 601 m_BaseBundleName = baseBundleName; 602 } // setI18NParameters() 603 604 /** 605 * Sets the configuration for the {@code INI} file. 606 * 607 * @param filename The path; can be {@code null}. 608 * @param flag The flag that indicates whether the configuration file 609 * must exist before the program starts.{@code true} if the file must 610 * exist, {@code false} if it will be created on startup. 611 * @param comment The comment; can be {@code null}. 612 */ 613 public final void setINIFileConfig( final String filename, final boolean flag, final String comment ) 614 { 615 m_INIFilePath = filename; 616 m_INIFileComment = isNull( m_INIFilePath ) ? null : comment; 617 m_INIFileMustExist = flag; 618 } // setINIFileConfig() 619 620 /** 621 * Set the method that is provided as a source for the initialisation of 622 * the properties of the configuration bean. 623 * 624 * @param method The method; can be {@code null}. 625 */ 626 public final void setInitDataMethod( final MethodSpec method ) { m_InitDataMethod = method; } 627 628 /** 629 * Set the name of the resource that is used to initialise the 630 * properties of the configuration bean. 631 * 632 * @param initDataResource The resource name; can be {@code null}. 633 */ 634 public final void setInitDataResource( final String initDataResource ) { m_InitDataResource = initDataResource; } 635 636 /** 637 * Sets the class for the {@code Preferences} change listener. 638 * 639 * @param listenerClass The listener class; can be {@code null}. 640 * 641 * @see org.tquadrat.foundation.config.PreferencesRoot#changeListenerClass() 642 */ 643 public final void setPreferenceChangeListenerClass( final TypeName listenerClass ) 644 { 645 m_PreferenceChangeListenerClass = listenerClass; 646 } // setPreferenceChangeListenerClass() 647 648 /** 649 * Sets the name for the preferences root node. 650 * 651 * @param name The name for the preferences root node. 652 */ 653 public final void setPreferencesRoot( final String name ) 654 { 655 m_PreferencesRoot = requireNotEmptyArgument( name, "name" ); 656 } // setPreferencesRoot() 657} 658// class CodeGenerationConfiguration 659 660/* 661 * End of File 662 */