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