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<String,Object></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 */