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 javax.lang.model.element.Modifier.FINAL; 022import static javax.lang.model.element.Modifier.PRIVATE; 023import static javax.lang.model.element.Modifier.PUBLIC; 024import static javax.lang.model.element.Modifier.STATIC; 025import static org.apiguardian.api.API.Status.MAINTAINED; 026import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.DEFAULT_ACCESSOR_TYPE; 027import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.ENUM_ACCESSOR_TYPE; 028import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.LIST_ACCESSOR_TYPE; 029import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MAP_ACCESSOR_TYPE; 030import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_MissingStringConverter; 031import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_MissingStringConverterWithType; 032import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_PreferencesNotConfigured; 033import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.SET_ACCESSOR_TYPE; 034import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.ALLOWS_PREFERENCES; 035import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_Accessors; 036import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_PreferenceChangeListener; 037import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_PreferencesRoot; 038import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_UserPreferences; 039import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_WriteLock; 040import static org.tquadrat.foundation.javacomposer.Primitives.VOID; 041import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.USE_OF_CONCRETE_CLASS; 042import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.createSuppressWarningsAnnotation; 043import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING; 044 045import java.util.HashMap; 046import java.util.Map; 047import java.util.Optional; 048import java.util.prefs.BackingStoreException; 049import java.util.prefs.Preferences; 050 051import org.apiguardian.api.API; 052import org.tquadrat.foundation.annotation.ClassVersion; 053import org.tquadrat.foundation.ap.CodeGenerationError; 054import org.tquadrat.foundation.config.spi.prefs.PreferenceAccessor; 055import org.tquadrat.foundation.config.spi.prefs.PreferencesException; 056import org.tquadrat.foundation.javacomposer.ClassName; 057import org.tquadrat.foundation.javacomposer.ParameterizedTypeName; 058import org.tquadrat.foundation.javacomposer.TypeName; 059import org.tquadrat.foundation.javacomposer.WildcardTypeName; 060import org.tquadrat.foundation.lang.Objects; 061 062/** 063 * The 064 * {@linkplain org.tquadrat.foundation.config.ap.impl.CodeBuilder code builder implementation} 065 * that connects the configuration bean to 066 * {@link java.util.prefs.Preferences}, 067 * as defined in 068 * {@link org.tquadrat.foundation.config.PreferencesBeanSpec}. 069 * 070 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 071 * @version $Id: PreferencesBeanBuilder.java 1105 2024-02-28 12:58:46Z tquadrat $ 072 * @UMLGraph.link 073 * @since 0.1.0 074 */ 075@ClassVersion( sourceVersion = "$Id: PreferencesBeanBuilder.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 076@API( status = MAINTAINED, since = "0.1.0" ) 077public final class PreferencesBeanBuilder extends CodeBuilderBase 078{ 079 /*--------------*\ 080 ====** Constructors **===================================================== 081 \*--------------*/ 082 /** 083 * Creates a new instance of {@code PreferencesBeanBuilder}. 084 * 085 * @param context The code generator context. 086 */ 087 public PreferencesBeanBuilder( final CodeGeneratorContext context ) 088 { 089 super( context ); 090 } // PreferencesBeanBuilder() 091 092 /*---------*\ 093 ====** Methods **========================================================== 094 \*---------*/ 095 /** 096 * {@inheritDoc} 097 */ 098 @SuppressWarnings( {"IfStatementWithTooManyBranches", "OverlyCoupledMethod", "OverlyLongMethod", "OverlyComplexMethod"} ) 099 @Override 100 public final void build() 101 { 102 //---* Add the field for the name of the preferences root *------------ 103 final var preferencesRoot = getComposer().fieldBuilder( String.class, STD_FIELD_PreferencesRoot.toString(), PUBLIC, FINAL, STATIC ) 104 .addJavadoc( 105 """ 106 The name for the Preferences instance: {@value}. 107 """ ) 108 .initializer( "$S", getConfiguration().getPreferencesRoot() ) 109 .build(); 110 addField( STD_FIELD_PreferencesRoot, preferencesRoot ); 111 112 //---* Create the field for the preferences root node *---------------- 113 final var userPreference = getComposer().fieldBuilder( Preferences.class, STD_FIELD_UserPreferences.toString(), PRIVATE, FINAL ) 114 .addJavadoc( 115 """ 116 The Preferences root node. 117 """ ) 118 .build(); 119 addField( STD_FIELD_UserPreferences, userPreference ); 120 121 //---* Add the registry for the accessor instances *------------------- 122 final var preferenceAccessorType = ParameterizedTypeName.from( ClassName.from( PreferenceAccessor.class ), WildcardTypeName.subtypeOf( Object.class ) ); 123 final var registryType = ParameterizedTypeName.from( ClassName.from( Map.class ), TypeName.from( String.class ), preferenceAccessorType ); 124 final var accessorRegistry = getComposer().fieldBuilder( registryType, STD_FIELD_Accessors.toString(), PRIVATE, FINAL ) 125 .addJavadoc( 126 """ 127 The registry for the preferences accessors. 128 """ ) 129 .initializer( "new $T<>()", HashMap.class ) 130 .build(); 131 addField( STD_FIELD_Accessors, accessorRegistry ); 132 133 final var writeLock = getField( STD_FIELD_WriteLock ); 134 135 //---* Initialise the field for the preferences root node *------------ 136 addConstructorCode( getComposer().codeBlockBuilder() 137 .add( 138 """ 139 140 /* 141 * Retrieve the USER Preferences. 142 */ 143 """ ) 144 .addStatement( "$N = userRoot().node( $N )", userPreference, preferencesRoot ) 145 .addStaticImport( Preferences.class, "userRoot" ) 146 .build() 147 ); 148 149 //---* Create the method that returns the preference *----------------- 150 final var returnType = ParameterizedTypeName.from( Optional.class, Preferences.class ); 151 final var method = getComposer().methodBuilder( "obtainPreferencesNode" ) 152 .addModifiers( PUBLIC, FINAL ) 153 .addAnnotation( Override.class ) 154 .returns( returnType ) 155 .addJavadoc( getComposer().createInheritDocComment() ) 156 .addStatement( "return $T.of( $N )", Optional.class, userPreference ) 157 .build(); 158 addMethod( method ); 159 160 //---* The builder for the code of the loadPreferences() method *------ 161 final var loadPrefsCodeBuilder = getComposer().codeBlockBuilder() 162 .beginControlFlow( """ 163 try( final var ignore = $N.lock() ) 164 """, writeLock ); 165 166 //---* Add the preference change listener support *-------------------- 167 final var prefsChangeListener = getConfiguration().getPreferenceChangeListenerClass(); 168 if( prefsChangeListener.isPresent() ) 169 { 170 //---* Create the field *------------------------------------------ 171 final var changeListener = getComposer().fieldBuilder( prefsChangeListener.get(), STD_FIELD_PreferenceChangeListener.toString(), PRIVATE ) 172 .addJavadoc( 173 """ 174 The listener for preference changes. 175 """ ) 176 .addAnnotation( createSuppressWarningsAnnotation( getComposer(), USE_OF_CONCRETE_CLASS ) ) 177 .initializer( "$L", "null" ) 178 .build(); 179 addField( STD_FIELD_PreferenceChangeListener, changeListener ); 180 181 /* 182 * The listener itself will be instantiated only when 183 * loadPreferences() is called the first time. 184 */ 185 loadPrefsCodeBuilder.add( 186 """ 187 /* 188 * Create the preference change listener. 189 */ 190 """ ) 191 .beginControlFlow( 192 """ 193 if( isNull( $N ) ) 194 """, changeListener 195 ) 196 .addStaticImport( Objects.class, "isNull" ) 197 .addStatement( "$N = new $T( $N, $N )", changeListener, prefsChangeListener.get(), accessorRegistry, writeLock ) 198 .addStatement( "$N.addPreferenceChangeListener( $N )", userPreference, changeListener ) 199 .endControlFlow(); 200 } 201 loadPrefsCodeBuilder.add( 202 """ 203 /* 204 * Synchronise the preferences backing store with the memory. 205 */ 206 """ 207 ) 208 .addStatement( "$N.sync()", userPreference ) 209 .add( 210 """ 211 212 /* 213 * Load the data. 214 */ 215 """ 216 ); 217 218 //---* The builder for the code of the updatePreferences() method *---- 219 final var updatePrefsCodeBuilder = getComposer().codeBlockBuilder() 220 .beginControlFlow( """ 221 try( final var ignore = $N.lock() ) 222 """, writeLock ); 223 224 addConstructorCode( getComposer().codeBlockOf( """ 225 226 /* 227 * Initialise the registry for the preference accessor instances. 228 */ 229 """ ) ); 230 231 //---* Process the properties *---------------------------------------- 232 PropertiesLoop: for( final var iterator = getProperties(); iterator.hasNext(); ) 233 { 234 final var propertySpec = iterator.next(); 235 /* 236 * Skip the properties that do not have a tie to the preferences. 237 */ 238 if( !propertySpec.hasFlag( ALLOWS_PREFERENCES ) ) continue PropertiesLoop; 239 240 final var name = propertySpec.getPropertyName(); 241 final var key = propertySpec.getPrefsKey().orElseThrow( () -> new CodeGenerationError( format( MSG_PreferencesNotConfigured, name ) ) ); 242 final var accessorClass = propertySpec.getPrefsAccessorClass().orElseThrow( () -> new CodeGenerationError( format( MSG_PreferencesNotConfigured, name ) ) ); 243 final var field = propertySpec.getFieldName(); 244 245 //---* Add the code for the Constructor *-------------------------- 246 final var getter = getComposer().lambdaBuilder() 247 .addCode( "$N", field ) 248 .build(); 249 final var setter = getComposer().lambdaBuilder() 250 .addParameter( "p" ) 251 .addCode( "$N = p", field ) 252 .build(); 253 final var codeBlockBuilder = getComposer().codeBlockBuilder(); 254 255 if( accessorClass.equals( ENUM_ACCESSOR_TYPE ) ) 256 { 257 final var propertyType = propertySpec.getPropertyType(); 258 codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, $4T.class, $5L, $6L ) )", accessorRegistry, key, accessorClass, propertyType, getter, setter ); 259 } 260 else if( accessorClass.equals( LIST_ACCESSOR_TYPE ) || accessorClass.equals( SET_ACCESSOR_TYPE ) ) 261 { 262 final var propertyType = (ParameterizedTypeName) propertySpec.getPropertyType(); 263 final var argumentType = propertyType.typeArguments().getFirst(); 264 final var stringConverterType = getStringConverter( argumentType ) 265 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverterWithType, name, argumentType.toString() ) ) ); 266 switch( determineStringConverterInstantiation( stringConverterType, false ) ) 267 { 268 case BY_INSTANCE -> codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, $4T.INSTANCE, $5L, $6L ) )", accessorRegistry, key, accessorClass, stringConverterType, getter, setter ); 269 case THROUGH_CONSTRUCTOR -> codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, new $4T(), $5L, $6L ) )", accessorRegistry, key, accessorClass, stringConverterType, getter, setter ); 270 case AS_ENUM -> codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, new $4T( $7T), $5L, $6L ) )", accessorRegistry, key, accessorClass, stringConverterType, getter, setter, propertyType ); 271 } 272 } 273 else if( accessorClass.equals( MAP_ACCESSOR_TYPE ) ) 274 { 275 final var propertyType = (ParameterizedTypeName) propertySpec.getPropertyType(); 276 final var argumentTypes = propertyType.typeArguments(); 277 final var keyStringConverterType = getStringConverter( argumentTypes.getFirst() ) 278 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverterWithType, name, argumentTypes.getFirst().toString() ) ) ); 279 final var keySnippet = 280 switch( determineStringConverterInstantiation( keyStringConverterType, false ) ) 281 { 282 case BY_INSTANCE -> "$4T.INSTANCE"; 283 case THROUGH_CONSTRUCTOR -> "new $4T()"; 284 case AS_ENUM -> EMPTY_STRING; 285 }; 286 final var valueStringConverterType = getStringConverter( argumentTypes.get( 1 ) ) 287 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverterWithType, name, argumentTypes.get( 1 ).toString() ) ) ); 288 final var valueSnippet = 289 switch( determineStringConverterInstantiation( valueStringConverterType, false ) ) 290 { 291 case BY_INSTANCE -> "$5T.INSTANCE"; 292 case THROUGH_CONSTRUCTOR -> "new $5T()"; 293 case AS_ENUM -> EMPTY_STRING; 294 }; 295 codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, %1$s, %2$s, $6L, $7L ) )".formatted( keySnippet, valueSnippet ), accessorRegistry, key, accessorClass, keyStringConverterType, valueStringConverterType, getter, setter ); 296 } 297 else if( accessorClass.equals( DEFAULT_ACCESSOR_TYPE ) ) 298 { 299 final var stringConverterType = propertySpec.getStringConverterClass() 300 .orElseThrow( () -> new CodeGenerationError( format( MSG_MissingStringConverter, name ) ) ); 301 switch( determineStringConverterInstantiation( stringConverterType, propertySpec.isEnum() ) ) 302 { 303 case BY_INSTANCE -> codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, $5L, $6L, $4T.INSTANCE ) )", accessorRegistry, key, accessorClass, stringConverterType, getter, setter ); 304 case THROUGH_CONSTRUCTOR -> codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, $5L, $6L, new $4T() ) )", accessorRegistry, key, accessorClass, stringConverterType, getter, setter ); 305 case AS_ENUM -> codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T<>( $2S, $5L, $6L, new $4T( $7T ) ) )", accessorRegistry, key, accessorClass, stringConverterType, getter, setter, propertySpec.getPropertyType() ); 306 } 307 } 308 else 309 { 310 codeBlockBuilder.addStatement( "$1N.put( $2S, new $3T( $2S, $4L, $5L ) )", accessorRegistry, key, accessorClass, getter, setter ); 311 } 312 addConstructorCode( codeBlockBuilder.build() ); 313 314 //---* Add the code for loadPreferences() *------------------------ 315 loadPrefsCodeBuilder.addStatement( "$N.get( $S ).readPreference( $N )", accessorRegistry, key, userPreference ); 316 317 //---* Add the code for updatePreferences() *---------------------- 318 updatePrefsCodeBuilder.addStatement( "$N.get( $S ).writePreference( $N )", accessorRegistry, key, userPreference ); 319 } // PropertiesLoop: 320 321 //---* Create the loadPreferences() method *--------------------------- 322 loadPrefsCodeBuilder.nextControlFlow( 323 """ 324 325 catch( final $T e ) 326 """, BackingStoreException.class ) 327 .addStatement( "throw new $T( e )", PreferencesException.class ) 328 .endControlFlow(); 329 final var loadPrefsMethod = getComposer().methodBuilder( "loadPreferences" ) 330 .addModifiers( PUBLIC, FINAL ) 331 .addAnnotation( Override.class ) 332 .returns( VOID ) 333 .addJavadoc( getComposer().createInheritDocComment() ) 334 .addCode( loadPrefsCodeBuilder.build() ) 335 .build(); 336 addMethod( loadPrefsMethod ); 337 338 //---* Create the updatePreferences() method *------------------------- 339 updatePrefsCodeBuilder.add( "\n" ) 340 .addStatement( "$N.flush()", userPreference ) 341 .nextControlFlow( 342 """ 343 344 catch( final $T e ) 345 """, BackingStoreException.class ) 346 .addStatement( "throw new $T( e )", PreferencesException.class ) 347 .endControlFlow(); 348 final var updatePrefsMethod = getComposer().methodBuilder( "updatePreferences" ) 349 .addModifiers( PUBLIC, FINAL ) 350 .addAnnotation( Override.class ) 351 .returns( VOID ) 352 .addJavadoc( getComposer().createInheritDocComment() ) 353 .addCode( updatePrefsCodeBuilder.build() ) 354 .build(); 355 addMethod( updatePrefsMethod ); 356 } // build() 357} 358// class PreferencesBeanBuilder 359 360/* 361 * End of File 362 */