001/* 002 * ============================================================================ 003 * Copyright © 2002-2021 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.specialprops; 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 org.apiguardian.api.API.Status.STABLE; 024import static org.tquadrat.foundation.config.SpecialPropertyType.CONFIG_PROPERTY_RESOURCEBUNDLE; 025import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_NoBaseBundleName; 026import static org.tquadrat.foundation.config.ap.ConfigAnnotationProcessor.MSG_ResourceBundleWrongReturnType; 027import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.EXEMPT_FROM_TOSTRING; 028import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.GETTER_RETURNS_OPTIONAL; 029import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_IS_MUTABLE; 030import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.PROPERTY_REQUIRES_SYNCHRONIZATION; 031import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_ReadLock; 032import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_ResourceLocale; 033import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_WriteLock; 034import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.FIELD_MAY_BE_FINAL; 035import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.createSuppressWarningsAnnotation; 036import static org.tquadrat.foundation.lang.Objects.nonNull; 037import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 038 039import java.util.MissingResourceException; 040import java.util.Optional; 041import java.util.ResourceBundle; 042import java.util.function.BiFunction; 043 044import org.apiguardian.api.API; 045import org.tquadrat.foundation.annotation.ClassVersion; 046import org.tquadrat.foundation.annotation.MountPoint; 047import org.tquadrat.foundation.ap.CodeGenerationError; 048import org.tquadrat.foundation.config.I18nSupport; 049import org.tquadrat.foundation.config.SpecialPropertyType; 050import org.tquadrat.foundation.config.ap.impl.CodeBuilder; 051import org.tquadrat.foundation.config.ap.impl.PropertySpecImpl; 052import org.tquadrat.foundation.javacomposer.ClassName; 053import org.tquadrat.foundation.javacomposer.FieldSpec; 054import org.tquadrat.foundation.javacomposer.MethodSpec; 055import org.tquadrat.foundation.javacomposer.ParameterizedTypeName; 056import org.tquadrat.foundation.javacomposer.TypeName; 057import org.tquadrat.foundation.lang.Objects; 058 059/** 060 * The implementation of 061 * {@link SpecialPropertySpecBase} 062 * for 063 * {@link SpecialPropertyType#CONFIG_PROPERTY_RESOURCEBUNDLE}. 064 * 065 * @version $Id: ResourceBundleProperty.java 1001 2022-01-29 16:42:15Z tquadrat $ 066 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 067 * @UMLGraph.link 068 * @since 0.1.0 069 */ 070@ClassVersion( sourceVersion = "$Id: ResourceBundleProperty.java 1001 2022-01-29 16:42:15Z tquadrat $" ) 071@API( status = STABLE, since = "0.1.0" ) 072public final class ResourceBundleProperty extends SpecialPropertySpecBase 073{ 074 /*--------------*\ 075 ====** Constructors **===================================================== 076 \*--------------*/ 077 /** 078 * Creates a new instance of {@code ResourceBundleProperty}. 079 */ 080 public ResourceBundleProperty() 081 { 082 super( CONFIG_PROPERTY_RESOURCEBUNDLE, EXEMPT_FROM_TOSTRING, GETTER_RETURNS_OPTIONAL, PROPERTY_IS_MUTABLE ); 083 } // ResourceBundleProperty() 084 085 /*---------*\ 086 ====** Methods **========================================================== 087 \*---------*/ 088 /** 089 * The implementation of the method that composes a field for the 090 * given property. 091 * 092 * @param codeBuilder The factory for the code generation. 093 * @param property The property. 094 * @return The field specification. 095 */ 096 @SuppressWarnings( {"UseOfConcreteClass", "StaticMethodOnlyUsedInOneClass", "TypeMayBeWeakened"} ) 097 private static FieldSpec composeField( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 098 { 099 final var composer = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer(); 100 101 final var builder = composer.fieldBuilder( property.getPropertyType(), property.getFieldName(), PRIVATE ) 102 .addJavadoc( 103 """ 104 Special Property: "$L". 105 """, property.getPropertyName() ) 106 .initializer( "null" ); 107 108 if( codeBuilder.getConfiguration().getInitDataMethod().isEmpty() && !codeBuilder.getConfiguration().implementInterface( I18nSupport.class ) && property.getSetterMethodName().isEmpty() ) 109 { 110 builder.addAnnotation( createSuppressWarningsAnnotation( codeBuilder.getComposer(), FIELD_MAY_BE_FINAL ) ); 111 } 112 113 //---* Create the return value *-------------------------------------- 114 final var retValue = builder.build(); 115 116 //---* Done *---------------------------------------------------------- 117 return retValue; 118 } // composeField() 119 120 /** 121 * <p>{@summary The implementation of the method that composes a getter 122 * for the given property.}</p> 123 * <p>The implementation details depend from the usage: with 124 * {@link I18nSupport}, 125 * it is a bit more complex.</p> 126 * 127 * @param codeBuilder The factory for the code generation. 128 * @param property The property. 129 * @return The method specification. 130 */ 131 @SuppressWarnings( {"OptionalGetWithoutIsPresent", "TypeMayBeWeakened", "UseOfConcreteClass"} ) 132 private static final MethodSpec composeGetter( final CodeBuilder codeBuilder, final PropertySpecImpl property ) 133 { 134 final var composer = requireNonNullArgument( codeBuilder, "codeBuilder" ).getComposer(); 135 136 //---* Obtain the builder *-------------------------------------------- 137 final var builder = property.getGetterBuilder() 138 .orElseGet( () -> composer.methodBuilder( property.getGetterMethodName().get() ) 139 .addAnnotation( Override.class ) 140 .addModifiers( PUBLIC ) 141 .returns( property.getGetterReturnType() ) 142 ); 143 builder.addModifiers( FINAL ) 144 .addJavadoc( composer.createInheritDocComment() ); 145 146 //---* Create the body *----------------------------------------------- 147 if( codeBuilder.getConfiguration().implementInterface( I18nSupport.class ) ) 148 { 149 if( !property.hasFlag( GETTER_RETURNS_OPTIONAL ) ) throw new CodeGenerationError( MSG_ResourceBundleWrongReturnType ); 150 151 /* 152 * This the value for the base bundle name, not the name of the 153 * field where the value is stored! 154 */ 155 final var baseBundleName = codeBuilder.getConfiguration().getBaseBundleName() 156 .orElseThrow( () -> new CodeGenerationError( MSG_NoBaseBundleName ) ); 157 158 /* 159 * I18nSupport does not allow a setter for the resource bundle, so 160 * we do not need the locking for the access to the resource 161 * bundle. But it may be necessary for the update. 162 */ 163 final var lock = property.hasFlag( PROPERTY_REQUIRES_SYNCHRONIZATION ) 164 ? codeBuilder.getField( STD_FIELD_WriteLock ) 165 : null; 166 builder.addStatement( "$1T bundle = null", ResourceBundle.class ) 167 .addStatement( "final var currentLocale = getLocale()" ) 168 .beginControlFlow( 169 """ 170 if( currentLocale.equals( $1N ) ) 171 """, STD_FIELD_ResourceLocale.toString() ) 172 .addStatement( "bundle = $1N", property.getFieldName() ) 173 .endControlFlow() 174 .beginControlFlow( 175 """ 176 if( isNull( bundle ) ) 177 """ ) 178 .addStaticImport( Objects.class, "isNull" ); 179 if( nonNull( lock) ) 180 { 181 builder.beginControlFlow( 182 """ 183 try( final var ignored = $N.lock() ) 184 """, lock ); 185 } 186 else 187 { 188 builder.beginControlFlow( 189 """ 190 try 191 """ ); 192 } 193 194 builder.addStatement( "var module = getClass().getModule()" ) 195 .beginControlFlow( 196 """ 197 if( module.isNamed() ) 198 """ ) 199 .addStatement( "bundle = $1T.getBundle( $2S, currentLocale, module )", ResourceBundle.class, baseBundleName ) 200 .nextControlFlow( 201 """ 202 203 else 204 """ ) 205 .addStatement( "bundle = $1T.getBundle( $2S, currentLocale )", ResourceBundle.class, baseBundleName ) 206 .endControlFlow() 207 .addStatement( "$1N = bundle", property.getFieldName() ) 208 .addStatement( "$1N = currentLocale", STD_FIELD_ResourceLocale.toString() ) 209 .nextControlFlow( 210 """ 211 212 catch( @$1T( "unused" ) final $2T e ) 213 """, SuppressWarnings.class, MissingResourceException.class ) 214 .addCode( """ 215 /* Deliberately ignored */ 216 """ ) 217 .endControlFlow() 218 .endControlFlow() 219 .addStatement( "final var retValue = $T.ofNullable( bundle )", Optional.class ) 220 .addCode( codeBuilder.getComposer().createReturnStatement() ); 221 } 222 else 223 { 224 /* 225 * Same, but without I18nSupport … 226 */ 227 //---* Add the locking *------------------------------------------- 228 final var lock = property.hasFlag( PROPERTY_REQUIRES_SYNCHRONIZATION ) && property.hasFlag( PROPERTY_IS_MUTABLE ) 229 ? codeBuilder.getField( STD_FIELD_ReadLock ) 230 : null; 231 if( nonNull( lock) ) builder.beginControlFlow( 232 """ 233 try( final var ignored = $N.lock() ) 234 """, lock ); 235 236 //---* Return the value *------------------------------------------ 237 if( property.hasFlag( GETTER_RETURNS_OPTIONAL ) ) 238 { 239 builder.addStatement( "return $1T.ofNullable( $2N )", Optional.class, property.getFieldName() ); 240 } 241 else 242 { 243 builder.addStatement( "return $1N", property.getFieldName() ); 244 } 245 246 //---* Cleanup *--------------------------------------------------- 247 if( nonNull( lock) ) builder.endControlFlow(); 248 } 249 250 //---* Create the return value *--------------------------------------- 251 final var retValue = builder.build(); 252 253 //---* Done *---------------------------------------------------------- 254 return retValue; 255 } // composeGetter() 256 257 /** 258 * {@inheritDoc} 259 */ 260 @Override 261 public final Optional<TypeName> getCLIValueHandlerClass() { return Optional.empty(); } 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override 267 public final Optional<BiFunction<CodeBuilder,PropertySpecImpl, FieldSpec>> getFieldComposer() 268 { 269 return Optional.of( ResourceBundleProperty::composeField ); 270 } // getFieldComposer() 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override 276 @MountPoint 277 public Optional<BiFunction<CodeBuilder,PropertySpecImpl,MethodSpec>> getGetterComposer() { return Optional.of( ResourceBundleProperty::composeGetter ); } 278 279 /** 280 * {@inheritDoc} 281 */ 282 @Override 283 public final TypeName getGetterReturnType() 284 { 285 final var rawType = ClassName.from( Optional.class ); 286 final var retValue = ParameterizedTypeName.from( rawType, getPropertyType() ); 287 288 //---* Done *---------------------------------------------------------- 289 return retValue; 290 } // getGetterReturnType() 291 292 /** 293 * {@inheritDoc} 294 */ 295 @Override 296 public final Optional<TypeName> getPrefsAccessorClass() { return Optional.empty(); } 297 298 /** 299 * {@inheritDoc} 300 */ 301 @Override 302 public final TypeName getPropertyType() { return ClassName.from( ResourceBundle.class ); } 303 304 /** 305 * {@inheritDoc} 306 */ 307 @Override 308 public final Optional<TypeName> getStringConverterClass() { return Optional.empty(); } 309} 310// class ResourceBundleProperty 311 312/* 313 * End of File 314 */