001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * 007 * Licensed to the public under the agreements of the GNU Lesser General Public 008 * License, version 3.0 (the "License"). You may obtain a copy of the License at 009 * 010 * http://www.gnu.org/licenses/lgpl.html 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 014 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 015 * License for the specific language governing permissions and limitations 016 * under the License. 017 */ 018 019package org.tquadrat.foundation.javacomposer.internal; 020 021import static java.lang.Boolean.FALSE; 022import static java.lang.Boolean.TRUE; 023import static java.util.stream.Collectors.joining; 024import static org.apiguardian.api.API.Status.INTERNAL; 025import static org.tquadrat.foundation.javacomposer.internal.TypeNameImpl.VOID_PRIMITIVE; 026import static org.tquadrat.foundation.lang.Objects.checkState; 027import static org.tquadrat.foundation.lang.Objects.hash; 028import static org.tquadrat.foundation.lang.Objects.isNull; 029import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 030 031import java.io.UncheckedIOException; 032import java.lang.reflect.Type; 033import java.util.ArrayList; 034import java.util.Collection; 035import java.util.List; 036import java.util.stream.Stream; 037 038import org.apiguardian.api.API; 039import org.tquadrat.foundation.annotation.ClassVersion; 040import org.tquadrat.foundation.exception.UnexpectedExceptionError; 041import org.tquadrat.foundation.exception.ValidationException; 042import org.tquadrat.foundation.javacomposer.CodeBlock; 043import org.tquadrat.foundation.javacomposer.JavaComposer; 044import org.tquadrat.foundation.javacomposer.LambdaSpec; 045import org.tquadrat.foundation.javacomposer.ParameterSpec; 046import org.tquadrat.foundation.javacomposer.TypeName; 047import org.tquadrat.foundation.lang.Lazy; 048 049/** 050 * The implementation for 051 * {@link LambdaSpec}. 052 * 053 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 054 * @version $Id: LambdaSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $ 055 * @since 0.0.5 056 * 057 * @UMLGraph.link 058 */ 059@ClassVersion( sourceVersion = "$Id: LambdaSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 060@API( status = INTERNAL, since = "0.0.5" ) 061public final class LambdaSpecImpl implements LambdaSpec 062{ 063 /*---------------*\ 064 ====** Inner Classes **==================================================== 065 \*---------------*/ 066 /** 067 * The implementation for 068 * {@link org.tquadrat.foundation.javacomposer.LambdaSpec.Builder}. 069 * 070 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 071 * @version $Id: LambdaSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $ 072 * @since 0.0.5 073 * 074 * @UMLGraph.link 075 */ 076 @ClassVersion( sourceVersion = "$Id: LambdaSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 077 @API( status = INTERNAL, since = "0.0.5" ) 078 public static final class BuilderImpl implements Builder 079 { 080 /*------------*\ 081 ====** Attributes **=================================================== 082 \*------------*/ 083 /** 084 * The code for the lambda body. 085 */ 086 @SuppressWarnings( "UseOfConcreteClass" ) 087 private final CodeBlockImpl.BuilderImpl m_Code; 088 089 /** 090 * The reference to the factory. 091 */ 092 @SuppressWarnings( "UseOfConcreteClass" ) 093 private final JavaComposer m_Composer; 094 095 /** 096 * Flag that indicates whether the parameter types will be inferred. 097 */ 098 private Boolean m_InferTypes = null; 099 100 /** 101 * The flag is used to determine the emit format. 102 */ 103 private int m_Lines = 0; 104 105 /** 106 * The parameters for the lambda. 107 */ 108 private final Collection<ParameterSpecImpl> m_Parameters = new ArrayList<>(); 109 110 /*--------------*\ 111 ====** Constructors **================================================= 112 \*--------------*/ 113 /** 114 * Creates a new {@code BuilderImpl} instance. 115 * 116 * @param composer The reference to the factory that created this 117 * builder instance. 118 */ 119 public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer ) 120 { 121 m_Composer = requireNonNullArgument( composer, "composer" ); 122 m_Code = (CodeBlockImpl.BuilderImpl) m_Composer.codeBlockBuilder(); 123 } // BuilderImpl() 124 125 /*---------*\ 126 ====** Methods **====================================================== 127 \*---------*/ 128 /** 129 * {@inheritDoc} 130 */ 131 @Override 132 public final BuilderImpl addCode( final CodeBlock codeBlock ) 133 { 134 m_Code.add( requireNonNullArgument( codeBlock, "codeBlock" ) ); 135 ++m_Lines; 136 if( m_Composer.addDebugOutput() ) ++m_Lines; 137 138 //---* Done *------------------------------------------------------ 139 return this; 140 } // addCode() 141 142 /** 143 * {@inheritDoc} 144 */ 145 @Override 146 public final BuilderImpl addCode( final String format, final Object... args ) 147 { 148 m_Code.add( format, args ); 149 ++m_Lines; 150 if( m_Composer.addDebugOutput() ) ++m_Lines; 151 152 //---* Done *------------------------------------------------------ 153 return this; 154 } // addCode() 155 156 /** 157 * {@inheritDoc} 158 */ 159 @Override 160 public final BuilderImpl addComment( final String format, final Object... args ) 161 { 162 m_Code.addWithoutDebugInfo( "// " + requireNonNullArgument( format, "format" ) + "\n", args ); 163 m_Lines += 2; 164 165 //---* Done *------------------------------------------------------ 166 return this; 167 } // addComment() 168 169 /** 170 * {@inheritDoc} 171 */ 172 @Override 173 public final BuilderImpl addParameter( final String name ) 174 { 175 return addParameter( m_Composer.parameterOf( VOID_PRIMITIVE, name ) ); 176 } // addParameter() 177 178 /** 179 * {@inheritDoc} 180 */ 181 @Override 182 public final BuilderImpl addParameter( final ParameterSpec parameterSpec ) 183 { 184 final var parameter = (ParameterSpecImpl) requireNonNullArgument( parameterSpec, "parameterSpec" ); 185 final var type = parameter.type(); 186 final var name = parameter.name(); 187 if( type == VOID_PRIMITIVE ) 188 { 189 checkState( isNull( m_InferTypes) || m_InferTypes.booleanValue(), () -> new ValidationException( "Not inferring types; type required for %s".formatted( name ) ) ); 190 m_InferTypes = TRUE; 191 } 192 else 193 { 194 checkState( isNull( m_InferTypes ) || !m_InferTypes.booleanValue(), () -> new ValidationException( "Inferring types; no type allowed for %s".formatted( name ) ) ); 195 m_InferTypes = FALSE; 196 } 197 m_Parameters.add( parameter ); 198 199 //---* Done *------------------------------------------------------ 200 return this; 201 } // addParameter() 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 public final BuilderImpl addParameter( final Type type, final String name ) 208 { 209 return addParameter( m_Composer.parameterOf( type, name ) ); 210 } // addParameter() 211 212 /** 213 * {@inheritDoc} 214 */ 215 @Override 216 public final BuilderImpl addParameter( final TypeName type, final String name ) 217 { 218 return addParameter( m_Composer.parameterOf( type, name ) ); 219 } // addParameter() 220 221 /** 222 * {@inheritDoc} 223 */ 224 @Override 225 public final BuilderImpl addParameters( final Iterable<? extends ParameterSpec> parameterSpecs ) 226 { 227 for( final var parameterSpec : requireNonNullArgument( parameterSpecs, "parameterSpecs" ) ) 228 { 229 m_Parameters.add( (ParameterSpecImpl) parameterSpec ); 230 } 231 232 //---* Done *------------------------------------------------------ 233 return this; 234 } // addParameters() 235 236 /** 237 * {@inheritDoc} 238 */ 239 @Override 240 public final BuilderImpl addStatement( final String format, final Object... args ) 241 { 242 m_Code.addStatement( format, args ); 243 m_Lines += 2; 244 if( m_Composer.addDebugOutput() ) ++m_Lines; 245 246 //---* Done *------------------------------------------------------ 247 return this; 248 } // addStatement() 249 250 /** 251 * {@inheritDoc} 252 */ 253 @Override 254 public final BuilderImpl beginControlFlow( final String controlFlow, final Object... args ) 255 { 256 m_Code.beginControlFlow( controlFlow, args ); 257 m_Lines += 2; 258 if( m_Composer.addDebugOutput() ) ++m_Lines; 259 260 //---* Done *------------------------------------------------------ 261 return this; 262 } // beginControlFlow() 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override 268 public final LambdaSpecImpl build() { return new LambdaSpecImpl( this ); } 269 270 /** 271 * Returns the code for the lambda body. 272 * 273 * @return The body code. 274 */ 275 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 276 public final CodeBlockImpl code() { return m_Code.build(); } 277 278 /** 279 * {@inheritDoc} 280 */ 281 @Override 282 public final BuilderImpl endControlFlow() 283 { 284 m_Code.endControlFlow(); 285 m_Lines += 2; 286 if( m_Composer.addDebugOutput() ) ++m_Lines; 287 288 //---* Done *------------------------------------------------------ 289 return this; 290 } // endControlFlow() 291 292 /** 293 * {@inheritDoc} 294 */ 295 @Override 296 public final BuilderImpl endControlFlow( final String controlFlow, final Object... args ) 297 { 298 m_Code.endControlFlow( controlFlow, args ); 299 m_Lines += 2; 300 if( m_Composer.addDebugOutput() ) ++m_Lines; 301 302 //---* Done *------------------------------------------------------ 303 return this; 304 } // endControlFlow() 305 306 /** 307 * Returns the flag that indicates whether the types of the parameters 308 * will be inferred. 309 * 310 * @return {@code true} if the parameter types are inferred, 311 * {@code false} if they are explicit. 312 */ 313 @SuppressWarnings( {"PublicMethodNotExposedInInterface", "BooleanMethodNameMustStartWithQuestion"} ) 314 public final boolean inferTypes() { return isNull( m_InferTypes ) || m_InferTypes.booleanValue(); } 315 316 /** 317 * Return the flag that indicates the emit format. 318 * 319 * @return {@code true} if the multi-line format with curly braces and 320 * a return statement is to be emitted, {@code false} if the single 321 * line format can be used. 322 */ 323 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 324 public final boolean isMultiLine() { return m_Lines > 1; } 325 326 /** 327 * {@inheritDoc} 328 */ 329 @Override 330 public final BuilderImpl nextControlFlow( final String controlFlow, final Object... args ) 331 { 332 m_Code.nextControlFlow( controlFlow, args ); 333 m_Lines += 2; 334 if( m_Composer.addDebugOutput() ) ++m_Lines; 335 336 //---* Done *------------------------------------------------------ 337 return this; 338 } // nextControlFlow() 339 340 /** 341 * Returns the parameters for the lambda. 342 * 343 * @return The parameters. 344 */ 345 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 346 public final List<ParameterSpecImpl> parameters() { return List.copyOf( m_Parameters ); } 347 } 348 // class BuilderImpl 349 350 /*------------*\ 351 ====** Attributes **======================================================= 352 \*------------*/ 353 /** 354 * Lazily initialised return value of 355 * {@link #toString()} 356 * for this instance. 357 */ 358 private final Lazy<String> m_CachedString; 359 360 /** 361 * The code of the body for this lambda. 362 */ 363 @SuppressWarnings( "UseOfConcreteClass" ) 364 private final CodeBlockImpl m_Code; 365 366 /** 367 * The reference to the factory. 368 */ 369 @SuppressWarnings( "UseOfConcreteClass" ) 370 private final JavaComposer m_Composer; 371 372 /** 373 * Flag that indicates whether the parameter types will be inferred. 374 */ 375 private final boolean m_InferTypes; 376 377 /** 378 * The flag that indicates the emit format. {@code true} stands for the 379 * multi-line format with curly braces and a return statement, 380 * {@code false} for the single line format. 381 */ 382 private final boolean m_IsMultiLine; 383 384 /** 385 * The parameters of this method. 386 */ 387 private final List<ParameterSpecImpl> m_Parameters; 388 389 /*--------------*\ 390 ====** Constructors **===================================================== 391 \*--------------*/ 392 /** 393 * Creates a new {@code LambdaSpecImpl} instance. 394 * 395 * @param builder The builder. 396 */ 397 @SuppressWarnings( "AccessingNonPublicFieldOfAnotherObject" ) 398 public LambdaSpecImpl( @SuppressWarnings( "UseOfConcreteClass" ) final BuilderImpl builder ) 399 { 400 m_Composer = builder.m_Composer; 401 m_Code = builder.code(); 402 m_Parameters = builder.parameters(); 403 m_InferTypes = builder.inferTypes(); 404 m_IsMultiLine = builder.isMultiLine(); 405 406 m_CachedString = Lazy.use( this::initializeCachedString ); 407 } // LambdaSpecImpl() 408 409 /*---------*\ 410 ====** Methods **========================================================== 411 \*---------*/ 412 /** 413 * Emits this lambda to the given code writer. 414 * 415 * @param codeWriter The code writer. 416 * @throws UncheckedIOException A problem occurred when writing to the 417 * output target. 418 */ 419 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 420 public final void emit( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter ) throws UncheckedIOException 421 { 422 //---* Get the parameters *-------------------------------------------- 423 if( m_Parameters.isEmpty() ) 424 { 425 codeWriter.emit( "()" ); 426 } 427 else if( m_Parameters.size() == 1 ) 428 { 429 final var parameter = m_Parameters.getFirst(); 430 final var name = parameter.name(); 431 final var type = parameter.type(); 432 433 if( m_InferTypes ) 434 { 435 codeWriter.emit( "$L", name ); 436 } 437 else 438 { 439 codeWriter.emit( "($T $L)", type, name ); 440 } 441 } 442 else 443 { 444 final String format; 445 final Object [] args; 446 if( m_InferTypes ) 447 { 448 format = m_Parameters.stream().map( $ -> "$L" ).collect( joining(",", "(", ")" ) ); 449 args = m_Parameters.stream().map( ParameterSpecImpl::name ).toArray(); 450 } 451 else 452 { 453 format = m_Parameters.stream().map( $ -> "$T $L" ).collect( joining(", ", "(", ")" ) ); 454 args = m_Parameters.stream().flatMap( p -> Stream.of( p.type(), p.name() ) ).toArray(); 455 } 456 codeWriter.emit( format, args ); 457 } 458 459 //---* The arrow operator *-------------------------------------------- 460 codeWriter.emit( " ->" ); 461 462 //---* The body *------------------------------------------------------ 463 if( m_Code.isEmpty() ) 464 { 465 codeWriter.emit( " null" ); 466 } 467 else 468 { 469 if( m_IsMultiLine ) 470 { 471 codeWriter.emit( "\n{\n" ) 472 .indent() 473 .emit( "$L", m_Code ) 474 .unindent() 475 .emit( "}" ); 476 } 477 else 478 { 479 codeWriter.emit( " $L", m_Code ); 480 } 481 } 482 } // emit() 483 484 /** 485 * {@inheritDoc} 486 */ 487 @Override 488 public final boolean equals( final Object o ) 489 { 490 var retValue = this == o; 491 if( !retValue && (o instanceof final LambdaSpecImpl other) ) 492 { 493 retValue = m_Composer.equals( other.m_Composer ) && toString().equals( o.toString() ); 494 } 495 496 //---* Done *---------------------------------------------------------- 497 return retValue; 498 } // equals() 499 500 /** 501 * Returns the 502 * {@link JavaComposer} 503 * factory. 504 * 505 * @return The reference to the factory. 506 */ 507 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 508 public final JavaComposer getFactory() { return m_Composer; } 509 510 /** 511 * {@inheritDoc} 512 */ 513 @Override 514 public final int hashCode() { return hash( m_Composer, toString() ); } 515 516 /** 517 * The initializer for 518 * {@link #m_CachedString}. 519 * 520 * @return The return value for 521 * {@link #toString()}. 522 */ 523 private final String initializeCachedString() 524 { 525 final var resultBuilder = new StringBuilder(); 526 final var codeWriter = new CodeWriter( m_Composer, resultBuilder ); 527 try 528 { 529 emit( codeWriter ); 530 } 531 catch( final UncheckedIOException e ) 532 { 533 throw new UnexpectedExceptionError( e.getCause() ); 534 } 535 final var retValue = resultBuilder.toString(); 536 537 //---* Done *---------------------------------------------------------- 538 return retValue; 539 } // initializeCachedString() 540 541 /** 542 * {@inheritDoc} 543 */ 544 @SuppressWarnings( "AccessingNonPublicFieldOfAnotherObject" ) 545 @Override 546 public final BuilderImpl toBuilder() 547 { 548 final var retValue = new BuilderImpl( m_Composer ); 549 retValue.m_Code.addWithoutDebugInfo( m_Code ); 550 retValue.m_Parameters.addAll( m_Parameters ); 551 retValue.m_InferTypes = m_Parameters.isEmpty() ? null : Boolean.valueOf( m_InferTypes ); 552 553 /* 554 * If the lambda is multiline, any positive number greater than 1 goes; 555 * we honour Douglas Adams and his 'Hitchhiker's Guide to the Galaxy'. 556 */ 557 //noinspection MagicNumber 558 retValue.m_Lines = m_Code.isEmpty() ? 0 : m_IsMultiLine ? 42 : 1; 559 560 //---* Done *---------------------------------------------------------- 561 return retValue; 562 } // toBuilder() 563 564 /** 565 * {@inheritDoc} 566 */ 567 @Override 568 public final String toString() { return m_CachedString.get(); } 569} 570// class LambdaSpecImpl 571 572/* 573 * End of File 574 */