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.codebuilders;
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.ap.PropertySpec.PropertyFlag.EXEMPT_FROM_MAP;
025import static org.tquadrat.foundation.config.ap.PropertySpec.PropertyFlag.GETTER_ON_MAP;
026import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_ReadLock;
027import static org.tquadrat.foundation.config.ap.impl.CodeBuilder.StandardField.STD_FIELD_Registry;
028import static org.tquadrat.foundation.javacomposer.Primitives.BOOLEAN;
029import static org.tquadrat.foundation.javacomposer.Primitives.INT;
030import static org.tquadrat.foundation.javacomposer.Primitives.VOID;
031import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.SIMPLIFY_STREAM_API_CALL_CHAIN;
032import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.UNLIKELY_ARG_TYPE;
033import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.createSuppressWarningsAnnotation;
034import static org.tquadrat.foundation.lang.Objects.nonNull;
035
036import java.util.Collection;
037import java.util.HashSet;
038import java.util.Map;
039import java.util.Set;
040import java.util.TreeMap;
041import java.util.function.Supplier;
042import java.util.stream.Collectors;
043
044import org.apiguardian.api.API;
045import org.tquadrat.foundation.annotation.ClassVersion;
046import org.tquadrat.foundation.config.ap.impl.CodeBuilder;
047import org.tquadrat.foundation.javacomposer.ClassName;
048import org.tquadrat.foundation.javacomposer.ParameterizedTypeName;
049import org.tquadrat.foundation.javacomposer.TypeName;
050import org.tquadrat.foundation.javacomposer.WildcardTypeName;
051import org.tquadrat.foundation.lang.Objects;
052
053/**
054 *  <p>{@summary The
055 *  {@linkplain CodeBuilder code builder implementation}
056 *  for the generation of the code that let the configuration bean
057 *  implement the interface
058 *  {@link java.util.Map}.}</p>
059 *  <p>More precisely, the configuration bean implements
060 *  <code>Map&lt;String,Object&gt;</code>, although this is not checked by the
061 *  annotation processor.</p>
062 *
063 *  @version $Id: MapImplementor.java 1061 2023-09-25 16:32:43Z tquadrat $
064 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
065 *  @UMLGraph.link
066 *  @since 0.1.0
067 */
068@ClassVersion( sourceVersion = "$Id: MapImplementor.java 1061 2023-09-25 16:32:43Z tquadrat $" )
069@API( status = STABLE, since = "0.1.0" )
070public final class MapImplementor extends CodeBuilderBase
071{
072        /*--------------*\
073    ====** Constructors **=====================================================
074        \*--------------*/
075    /**
076     *  Creates a new instance of {@code MapImplementor}.
077     *
078     *  @param  context The code generator context.
079     */
080    public MapImplementor( final CodeGeneratorContext context )
081    {
082        super( context );
083    }   //  MapImplementor()
084
085        /*---------*\
086    ====** Methods **==========================================================
087        \*---------*/
088    /**
089     *  {@inheritDoc}
090     */
091    @SuppressWarnings( {"OverlyCoupledMethod", "OverlyLongMethod", "OverlyComplexMethod"} )
092    @Override
093    public final void build()
094    {
095        //---* Create the field *----------------------------------------------
096        final var objectType = WildcardTypeName.subtypeOf( Object.class );
097        final var supplierType = ParameterizedTypeName.from( ClassName.from( Supplier.class ), objectType );
098        final var registryType = ParameterizedTypeName.from( ClassName.from( Map.class ), TypeName.from( String.class ), supplierType );
099        final var registry = getComposer().fieldBuilder( registryType, STD_FIELD_Registry.toString(), PRIVATE, FINAL )
100            .addJavadoc(
101                """
102                The shadow map for the properties, used for the implementation of the
103                {@link Map}
104                interface.
105                """ )
106            .initializer( "new $1T<>()", TreeMap.class )
107            .build();
108        addField( STD_FIELD_Registry, registry );
109
110        //---* Create the code for the constructor *---------------------------
111        final var builder = getComposer().codeBlockBuilder()
112            .add( """
113
114                /*
115                 * Initialising the shadow map.
116                 */
117                """ );
118        PropertyLoop: for( final var iterator = getProperties(); iterator.hasNext(); )
119        {
120            final var propertySpec = iterator.next().merge();
121
122            if( propertySpec.hasFlag( EXEMPT_FROM_MAP ) ) continue PropertyLoop;
123
124            //---* Create the supplier and add it to the registry *------------
125            if( !propertySpec.hasFlag( GETTER_ON_MAP ) )
126            {
127                final var field = propertySpec.getFieldName();
128                final var supplier = getComposer().lambdaBuilder()
129                    .addCode( "$1N", field )
130                    .build();
131                builder.addStatement( "$1N.put( $2S, $3L )", registry, propertySpec.getPropertyName(), supplier );
132            }
133            else
134            {
135                propertySpec.getGetterMethodName()
136                    .ifPresent( method -> builder.addStatement( "$1N.put( $2S, this::$3L )", registry, propertySpec.getPropertyName(), method  ) );
137            }
138        }   //  PropertyLoop:
139        addConstructorCode( builder.build() );
140
141        //---* Add the methods from Map *--------------------------------------
142        final var throwException = getComposer().statementOf( "throw new $1T()", UnsupportedOperationException.class );
143        final var inheritDocComment = getComposer().createInheritDocComment();
144        final var lock = isSynchronized()
145             ? getField( STD_FIELD_ReadLock )
146             : null;
147
148        var method = getComposer().methodBuilder( "clear" )
149            .addModifiers( PUBLIC, FINAL )
150            .addAnnotation( Override.class )
151            .returns( VOID )
152            .addJavadoc( inheritDocComment )
153            .addCode( throwException )
154            .build();
155        addMethod( method );
156
157        var arg0 = getComposer().parameterBuilder( Object.class, "key", FINAL )
158            .build();
159        method = getComposer().methodBuilder( "containsKey" )
160            .addModifiers( PUBLIC, FINAL )
161            .addAnnotation( createSuppressWarningsAnnotation( getComposer(), UNLIKELY_ARG_TYPE ) )
162            .addAnnotation( Override.class )
163            .addParameter( arg0 )
164            .returns( BOOLEAN )
165            .addJavadoc( inheritDocComment )
166            .addStatement( "return $1N.containsKey( $2N )", registry, arg0 )
167            .build();
168        addMethod( method );
169
170        arg0 = getComposer().parameterBuilder( Object.class, "value", FINAL )
171            .build();
172        method = getComposer().methodBuilder( "containsValue" )
173            .addModifiers( PUBLIC, FINAL )
174            .addAnnotation( Override.class )
175            .addParameter( arg0 )
176            .returns( BOOLEAN )
177            .addJavadoc( inheritDocComment )
178            .addStatement( "return values().stream().anyMatch( v -> $1T.equals( v, $2N ) )", Objects.class, arg0 )
179            .build();
180        addMethod( method );
181
182        TypeName returnType = ParameterizedTypeName.from( ClassName.from( Set.class ), ParameterizedTypeName.from( Map.Entry.class, String.class, Object.class ) );
183        var methodBuilder = getComposer().methodBuilder( "entrySet" )
184            .addModifiers( PUBLIC, FINAL )
185            .addAnnotation( Override.class )
186            .returns( returnType )
187            .addJavadoc( inheritDocComment )
188            .addStatement( "final $1T retValue = new $2T<>()", returnType, HashSet.class );
189        if( nonNull( lock) ) methodBuilder.beginControlFlow(
190            """
191            try( final var ignored = $N.lock() )
192            """, lock );
193        methodBuilder.beginControlFlow(
194            """
195            for( final var entry : $1N.entrySet() )
196            """, registry )
197            .addStatement( "final var key = entry.getKey()" )
198            .addStatement( "final var value = entry.getValue().get()" )
199            .addStatement( "retValue.add( $1T.entry( key, value ) )", Map.class )
200            .endControlFlow();
201        if( nonNull( lock ) ) methodBuilder.endControlFlow();
202        method = methodBuilder.addCode( getComposer().createReturnStatement() )
203            .build();
204        addMethod( method );
205
206        arg0 = getComposer().parameterBuilder( Object.class, "o", FINAL )
207            .build();
208        method = getComposer().methodBuilder( "equals" )
209            .addModifiers( PUBLIC, FINAL )
210            .addAnnotation( Override.class )
211            .addParameter( arg0 )
212            .returns( BOOLEAN )
213            .addJavadoc( inheritDocComment )
214            .addStatement( "return this == $1N", arg0 )
215            .build();
216        addMethod( method );
217
218        arg0 = getComposer().parameterBuilder( Object.class, "key", FINAL )
219            .build();
220        methodBuilder = getComposer().methodBuilder( "get" )
221            .addModifiers( PUBLIC, FINAL )
222            .addAnnotation( createSuppressWarningsAnnotation( getComposer(), UNLIKELY_ARG_TYPE ) )
223            .addAnnotation( Override.class )
224            .addParameter( arg0 )
225            .returns( Object.class )
226            .addJavadoc( inheritDocComment )
227            .addStatement( "$1T retValue = null", Object.class )
228            .addStatement( "final var supplier = $1N.get( $2N )", registry, arg0 )
229            .beginControlFlow(
230                """
231                if( nonNull( supplier) )
232                """ )
233            .addStaticImport( Objects.class, "nonNull" );
234        if( nonNull( lock) ) methodBuilder.beginControlFlow(
235            """
236            try( final var ignored = $N.lock() )
237            """, lock );
238        methodBuilder.addStatement( "retValue = supplier.get()" );
239        if( nonNull( lock ) ) methodBuilder.endControlFlow();
240        method = methodBuilder.endControlFlow()
241            .addCode( getComposer().createReturnStatement() )
242            .build();
243        addMethod( method );
244
245        method = getComposer().methodBuilder( "hashCode" )
246            .addModifiers( PUBLIC, FINAL )
247            .addAnnotation( Override.class )
248            .returns( INT )
249            .addJavadoc( inheritDocComment )
250            .addStatement( "return $1N.hashCode()", registry )
251            .build();
252        addMethod( method );
253
254        method = getComposer().methodBuilder( "isEmpty" )
255            .addModifiers( PUBLIC, FINAL )
256            .addAnnotation( Override.class )
257            .returns( BOOLEAN )
258            .addJavadoc( inheritDocComment )
259            .addStatement( "return $1N.isEmpty()", registry )
260            .build();
261        addMethod( method );
262
263        returnType = ParameterizedTypeName.from( Set.class, String.class );
264        method = getComposer().methodBuilder( "keySet" )
265            .addModifiers( PUBLIC, FINAL )
266            .addAnnotation( Override.class )
267            .returns( returnType )
268            .addJavadoc( inheritDocComment )
269            .addStatement( "return $N.keySet()", registry )
270            .build();
271        addMethod( method );
272
273        arg0 = getComposer().parameterBuilder( String.class, "key", FINAL )
274            .build();
275        method = getComposer().methodBuilder( "put" )
276            .addModifiers( PUBLIC, FINAL )
277            .addAnnotation( Override.class )
278            .addParameter( arg0 )
279            .addParameter( Object.class, "value", FINAL )
280            .returns( Object.class )
281            .addJavadoc( inheritDocComment )
282            .addCode( throwException )
283            .build();
284        addMethod( method );
285
286        final TypeName argType = ParameterizedTypeName.from( ClassName.from( Map.class ), WildcardTypeName.subtypeOf( String.class ), WildcardTypeName.subtypeOf( Object.class ) );
287        arg0 = getComposer().parameterBuilder( argType, "m", FINAL )
288            .build();
289        method = getComposer().methodBuilder( "putAll" )
290            .addModifiers( PUBLIC, FINAL )
291            .addAnnotation( Override.class )
292            .addParameter( arg0 )
293            .addJavadoc( inheritDocComment )
294            .addCode( throwException )
295            .build();
296        addMethod( method );
297
298        arg0 = getComposer().parameterBuilder( Object.class, "key", FINAL )
299            .build();
300        method = getComposer().methodBuilder( "remove" )
301            .addModifiers( PUBLIC, FINAL )
302            .addAnnotation( Override.class )
303            .addParameter( arg0 )
304            .returns( Object.class )
305            .addJavadoc( inheritDocComment )
306            .addCode( throwException )
307            .build();
308        addMethod( method );
309
310        method = getComposer().methodBuilder( "size" )
311            .addModifiers( PUBLIC, FINAL )
312            .addAnnotation( Override.class )
313            .returns( INT )
314            .addJavadoc( inheritDocComment )
315            .addStatement( "return $1N.size()", registry )
316            .build();
317        addMethod( method );
318
319        returnType = ParameterizedTypeName.from( Collection.class, Object.class );
320        methodBuilder = getComposer().methodBuilder( "values" )
321            .addModifiers( PUBLIC, FINAL )
322            .addAnnotation( Override.class )
323            .addAnnotation( createSuppressWarningsAnnotation( getComposer(), SIMPLIFY_STREAM_API_CALL_CHAIN ) )
324            .returns( returnType )
325            .addJavadoc( inheritDocComment )
326            .addStatement( "final $1T retValue", returnType );
327        if( nonNull( lock) ) methodBuilder.beginControlFlow(
328            """
329            try( final var ignored = $N.lock() )
330            """, lock );
331        methodBuilder.addStatement( """
332            retValue = $1N.values()
333                .stream()
334                .map( $2T::get )
335                .collect( toUnmodifiableList() )\
336            """, registry, Supplier.class )
337            .addStaticImport( Collectors.class, "toUnmodifiableList" );
338        if( nonNull( lock ) ) methodBuilder.endControlFlow();
339        method = methodBuilder.addCode( getComposer().createReturnStatement() )
340            .build();
341        addMethod( method );
342    }   //  build()
343}
344//  class MapImplementor
345
346/*
347 *  End of File
348 */