001/* 002 * ============================================================================ 003 * Copyright © 2015 Square, Inc. 004 * Copyright for the modifications © 2018-2024 by Thomas Thrien. 005 * ============================================================================ 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020package org.tquadrat.foundation.javacomposer.internal; 021 022import static java.lang.String.CASE_INSENSITIVE_ORDER; 023import static java.util.Comparator.comparing; 024import static java.util.Locale.ROOT; 025import static javax.lang.model.element.Modifier.ABSTRACT; 026import static javax.lang.model.element.Modifier.DEFAULT; 027import static javax.lang.model.element.Modifier.FINAL; 028import static javax.lang.model.element.Modifier.PRIVATE; 029import static javax.lang.model.element.Modifier.PUBLIC; 030import static javax.lang.model.element.Modifier.STATIC; 031import static org.apiguardian.api.API.Status.INTERNAL; 032import static org.apiguardian.api.API.Status.STABLE; 033import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.CLASS_WITH_TOO_MANY_METHODS; 034import static org.tquadrat.foundation.javacomposer.internal.ClassNameImpl.OBJECT; 035import static org.tquadrat.foundation.javacomposer.internal.TypeNameImpl.VOID_PRIMITIVE; 036import static org.tquadrat.foundation.javacomposer.internal.TypeSpecImpl.Kind.CLASS; 037import static org.tquadrat.foundation.javacomposer.internal.Util.createDebugOutput; 038import static org.tquadrat.foundation.javacomposer.internal.Util.union; 039import static org.tquadrat.foundation.lang.Objects.checkState; 040import static org.tquadrat.foundation.lang.Objects.hash; 041import static org.tquadrat.foundation.lang.Objects.isNull; 042import static org.tquadrat.foundation.lang.Objects.nonNull; 043import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 044import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 045import static org.tquadrat.foundation.lang.Objects.requireValidNonNullArgument; 046import static org.tquadrat.foundation.util.StringUtils.capitalize; 047import static org.tquadrat.foundation.util.StringUtils.decapitalize; 048import static org.tquadrat.foundation.util.StringUtils.isEmpty; 049import static org.tquadrat.foundation.util.StringUtils.isNotEmptyOrBlank; 050 051import javax.lang.model.element.Modifier; 052import java.io.UncheckedIOException; 053import java.util.ArrayList; 054import java.util.Collection; 055import java.util.EnumSet; 056import java.util.HashSet; 057import java.util.List; 058import java.util.Optional; 059import java.util.Set; 060 061import org.apiguardian.api.API; 062import org.tquadrat.foundation.annotation.ClassVersion; 063import org.tquadrat.foundation.exception.ValidationException; 064import org.tquadrat.foundation.javacomposer.CodeBlock; 065import org.tquadrat.foundation.javacomposer.FieldSpec; 066import org.tquadrat.foundation.javacomposer.JavaComposer; 067import org.tquadrat.foundation.javacomposer.MethodSpec; 068import org.tquadrat.foundation.javacomposer.SuppressableWarnings; 069import org.tquadrat.foundation.javacomposer.TypeName; 070import org.tquadrat.foundation.javacomposer.TypeSpec; 071 072/** 073 * The implementation of 074 * {@link TypeSpec} 075 * for a class. 076 * 077 * @author Square,Inc. 078 * @modified Thomas Thrien - thomas.thrien@tquadrat.org 079 * @version $Id: ClassSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $ 080 * @since 0.2.0 081 * 082 * @UMLGraph.link 083 */ 084@ClassVersion( sourceVersion = "$Id: ClassSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 085@API( status = INTERNAL, since = "0.2.0" ) 086public final class ClassSpecImpl extends TypeSpecImpl 087{ 088 /*---------------*\ 089 ====** Inner Classes **==================================================== 090 \*---------------*/ 091 /** 092 * The implementation of 093 * {@link TypeSpec.Builder} 094 * for a class. 095 * 096 * @author Square,Inc. 097 * @modified Thomas Thrien - thomas.thrien@tquadrat.org 098 * @version $Id: ClassSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $ 099 * @since 0.2.0 100 * 101 * @UMLGraph.link 102 */ 103 @ClassVersion( sourceVersion = "$Id: ClassSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 104 @API( status = INTERNAL, since = "0.2.0" ) 105 public static final class BuilderImpl extends TypeSpecImpl.BuilderImpl 106 { 107 /*------------*\ 108 ====** Attributes **=================================================== 109 \*------------*/ 110 /** 111 * The anonymous type arguments. 112 */ 113 @SuppressWarnings( "UseOfConcreteClass" ) 114 private final CodeBlockImpl m_AnonymousTypeArguments; 115 116 /*--------------*\ 117 ====** Constructors **================================================= 118 \*--------------*/ 119 /** 120 * Creates a new {@code BuilderImpl} instance. 121 * 122 * @param composer The reference to the factory that created this 123 * builder instance. 124 * @param name The name of the type to build. 125 * @param anonymousTypeArguments Anonymous type arguments. 126 */ 127 @SuppressWarnings( "UseOfConcreteClass" ) 128 public BuilderImpl( final JavaComposer composer, final CharSequence name, final CodeBlockImpl anonymousTypeArguments ) 129 { 130 this( composer, Optional.of( requireNotEmptyArgument( name, "name" ).toString() ), anonymousTypeArguments ); 131 } // BuilderImpl() 132 133 /** 134 * Creates a new {@code BuilderImpl} instance for an anonymous type. 135 * 136 * @param composer The reference to the factory that created this 137 * builder instance. 138 * @param anonymousTypeArguments Anonymous type arguments. 139 */ 140 @SuppressWarnings( "UseOfConcreteClass" ) 141 public BuilderImpl( final JavaComposer composer, final CodeBlockImpl anonymousTypeArguments ) 142 { 143 this( composer, Optional.empty(), anonymousTypeArguments ); 144 } // BuilderImpl() 145 146 /** 147 * Creates a new {@code BuilderImpl} instance. 148 * 149 * @param composer The reference to the factory that created this 150 * builder instance. 151 * @param name The name of the type to build. 152 * @param anonymousTypeArguments Anonymous type arguments. 153 */ 154 @SuppressWarnings( {"OptionalUsedAsFieldOrParameterType"} ) 155 public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer, final Optional<String> name, @SuppressWarnings( "UseOfConcreteClass" ) final CodeBlockImpl anonymousTypeArguments ) 156 { 157 super( composer, CLASS, name ); 158 m_AnonymousTypeArguments = anonymousTypeArguments; 159 } // BuilderImpl() 160 161 /*---------*\ 162 ====** Methods **====================================================== 163 \*---------*/ 164 /** 165 * {@inheritDoc} 166 */ 167 @API( status = STABLE, since = "0.2.0" ) 168 @Override 169 public final BuilderImpl addAttribute( final FieldSpec fieldSpec, final boolean readOnly ) 170 { 171 final var fieldSpecImpl = (FieldSpecImpl) requireValidNonNullArgument( fieldSpec, "fieldSpec", v -> v.hasModifier( PRIVATE ), $ -> "Field %s needs to be private".formatted( fieldSpec.name() ) ); 172 addField( fieldSpecImpl ); 173 174 final var fieldName = fieldSpecImpl.name(); 175 final var propertyName = decapitalize( fieldName.startsWith( "m_" ) ? fieldName.substring( 2 ) : fieldName ); 176 177 final Set<Modifier> modifiers = EnumSet.of( PUBLIC, FINAL ); 178 if( fieldSpec.hasModifier( STATIC ) ) modifiers.add( STATIC ); 179 180 final var accessor = getFactory().methodBuilder( propertyName ) 181 .addModifiers( modifiers ) 182 .returns( fieldSpecImpl.type() ) 183 .addStatement( "return $N", fieldSpecImpl ) 184 .build(); 185 addMethod( accessor ); 186 187 if( !(readOnly || fieldSpecImpl.hasModifier( FINAL )) ) 188 { 189 final var param = getFactory().parameterBuilder( fieldSpecImpl.type(), "value", FINAL ) 190 .build(); 191 final var setter = getFactory().methodBuilder( propertyName ) 192 .addModifiers( modifiers ) 193 .addParameter( param ) 194 .returns( VOID_PRIMITIVE ) 195 .addStatement( "$N = $N", fieldSpecImpl, param ) 196 .build(); 197 addMethod( setter ); 198 } 199 200 //---* Done *------------------------------------------------------ 201 return this; 202 } // addAttribute() 203 204 /** 205 * {@inheritDoc} 206 */ 207 @Override 208 public final BuilderImpl addMethod( final MethodSpec methodSpec ) 209 { 210 final var methodSpecImpl = (MethodSpecImpl) requireNonNullArgument( methodSpec, "methodSpec" ); 211 checkState( methodSpecImpl.defaultValue().isEmpty(), () -> new IllegalStateException( "%s %s.%s cannot have a default value".formatted( CLASS, getName().orElse( NAME_ANONYMOUS_TYPE ), methodSpecImpl.name() ) ) ); 212 checkState( !methodSpecImpl.hasModifier( DEFAULT ), () -> new IllegalStateException( "%s %s.%s cannot be default".formatted( CLASS, getName().orElse( NAME_ANONYMOUS_TYPE ), methodSpecImpl.name() ) ) ); 213 214 final var methodSpecs = getMethodSpecs(); 215 if( composer().addDebugOutput() ) 216 { 217 final var builder = methodSpecImpl.toBuilder( false ); 218 createDebugOutput( true ).ifPresent( debug -> builder.addJavadoc( "\n$L\n", debug.asLiteral() ) ); 219 methodSpecs.add( builder.build() ); 220 } 221 else 222 { 223 methodSpecs.add( methodSpecImpl ); 224 } 225 226 final var maxMethods = composer().getMaxMethods(); 227 if( (maxMethods > 0) && (methodSpecs.size() >= maxMethods) ) 228 { 229 addSuppressableWarning( CLASS_WITH_TOO_MANY_METHODS ); 230 } 231 232 //---* Done *------------------------------------------------------ 233 return this; 234 } // addMethod() 235 236 /** 237 * {@inheritDoc} 238 */ 239 @Override 240 public final Builder addProperty( final FieldSpec fieldSpec, final boolean readOnly ) 241 { 242 final var fieldSpecImpl = (FieldSpecImpl) requireValidNonNullArgument( fieldSpec, "fieldSpec", v -> v.hasModifier( PRIVATE ), $ -> "Field %s needs to be private".formatted( fieldSpec.name() ) ); 243 addField( fieldSpecImpl ); 244 245 final var fieldName = fieldSpecImpl.name(); 246 final var propertyName = fieldName.startsWith( "m_" ) ? fieldName.substring( 2 ) : capitalize( fieldName ); 247 248 final Set<Modifier> modifiers = EnumSet.of( PUBLIC, FINAL ); 249 if( fieldSpec.hasModifier( STATIC ) ) modifiers.add( STATIC ); 250 251 final var getter = getFactory().methodBuilder( "get" + propertyName ) 252 .addModifiers( modifiers ) 253 .returns( fieldSpecImpl.type() ) 254 .addStatement( "return $N", fieldSpecImpl ) 255 .build(); 256 addMethod( getter ); 257 258 if( !(readOnly || fieldSpecImpl.hasModifier( FINAL )) ) 259 { 260 final var param = getFactory().parameterBuilder( fieldSpecImpl.type(), "value", FINAL ) 261 .build(); 262 final var setter = getFactory().methodBuilder( "set" + propertyName ) 263 .addModifiers( modifiers ) 264 .addParameter( param ) 265 .returns( VOID_PRIMITIVE ) 266 .addStatement( "$N = $N", fieldSpecImpl, param ) 267 .build(); 268 addMethod( setter ); 269 } 270 271 //---* Done *------------------------------------------------------ 272 return this; 273 } // addProperty() 274 275 /** 276 * {@inheritDoc} 277 */ 278 @Override 279 public final ClassSpecImpl build() 280 { 281 final var isAbstract = getModifiers().contains( ABSTRACT ); 282 283 for( final var methodSpec : getMethodSpecs() ) 284 { 285 checkState( isAbstract || !methodSpec.hasModifier( ABSTRACT ), () -> new ValidationException( "non-abstract type %s cannot declare abstract method %s".formatted( getName().orElse( NAME_ANONYMOUS_TYPE ), methodSpec.name() ) ) ); 286 } 287 288 final var superclassIsObject = getSuperclass().equals( OBJECT ); 289 final var interestingSupertypeCount = (superclassIsObject ? 0 : 1) + getSuperinterfaces().size(); 290 checkState( getAnonymousTypeArguments().isEmpty() || interestingSupertypeCount <= 1, () -> new ValidationException( "anonymous type has too many supertypes" ) ); 291 292 final var retValue = new ClassSpecImpl( this ); 293 294 //---* Done *------------------------------------------------------ 295 return retValue; 296 } // build() 297 298 /** 299 * {@inheritDoc} 300 */ 301 @Override 302 protected final Optional<CodeBlockImpl> getAnonymousTypeArguments() { return Optional.ofNullable( m_AnonymousTypeArguments ); } 303 304 /** 305 * {@inheritDoc} 306 */ 307 @Override 308 public final boolean isAnonymousClass() { return nonNull( m_AnonymousTypeArguments ); } 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override 314 public final BuilderImpl superclass( final TypeName superclass ) 315 { 316 super.superclass( superclass ); 317 318 //---* Done *------------------------------------------------------ 319 return this; 320 } // superclass() 321 } 322 // class BuilderImpl 323 324 /*------------*\ 325 ====** Attributes **======================================================= 326 \*------------*/ 327 /** 328 * The anonymous type arguments for this type. 329 */ 330 @SuppressWarnings( "UseOfConcreteClass" ) 331 private final CodeBlockImpl m_AnonymousTypeArguments; 332 333 /*--------------*\ 334 ====** Constructors **===================================================== 335 \*--------------*/ 336 /** 337 * Creates a new {@code TypeClassSpecImpl} instance. 338 * 339 * @param builder The builder for this instance. 340 */ 341 @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject", "UseOfConcreteClass"} ) 342 public ClassSpecImpl( final BuilderImpl builder ) 343 { 344 super( builder ); 345 m_AnonymousTypeArguments = builder.m_AnonymousTypeArguments; 346 } // ClassSpecImpl() 347 348 /** 349 * Creates a dummy type spec for type-resolution in CodeWriter only while 350 * emitting the type declaration but before entering the type body. 351 * 352 * @param type The source type. 353 */ 354 @SuppressWarnings( {"UseOfConcreteClass"} ) 355 private ClassSpecImpl( final ClassSpecImpl type ) 356 { 357 super( type ); 358 assert isNull( type.m_AnonymousTypeArguments ) : "Not for anonymous classes"; 359 m_AnonymousTypeArguments = type.m_AnonymousTypeArguments; 360 } // ClassSpecImpl() 361 362 /*---------*\ 363 ====** Methods **========================================================== 364 \*---------*/ 365 /** 366 * {@inheritDoc} 367 */ 368 @Override 369 public final Optional<CodeBlock> anonymousTypeArguments() 370 { 371 return Optional.ofNullable( m_AnonymousTypeArguments ); 372 } // anonymousTypeArguments() 373 374 /** 375 * {@inheritDoc} 376 */ 377 @Override 378 protected final TypeSpecImpl createCopy() { return new ClassSpecImpl( this ); } 379 380 /** 381 * Emits the type to the given code writer, using the layout as defined 382 * by the Foundation library code. 383 * 384 * @param codeWriter The target code writer. 385 * @param enumName The name of the enum; can be {@code null}. 386 * @param implicitModifiers The implicit modifiers. 387 * @throws UncheckedIOException A problem occurred when writing to the 388 * output target. 389 */ 390 @SuppressWarnings( {"BoundedWildcard", "OptionalGetWithoutIsPresent", "MethodWithMultipleReturnPoints", "OverlyLongMethod", "OverlyComplexMethod"} ) 391 @Override 392 protected final void emit4Foundation( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter, final String enumName, final Set<Modifier> implicitModifiers ) throws UncheckedIOException 393 { 394 if( isNotEmptyOrBlank( enumName ) ) 395 { 396 //---* Emit an enum constant *------------------------------------- 397 /* 398 * enum constants are implemented as anonymous types so this is 399 * called from an outer type spec. 400 */ 401 codeWriter.emitJavadoc( getJavadoc() ); 402 final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() ); 403 getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() ) 404 .map( a -> (AnnotationSpecImpl) a ) 405 .ifPresent( annotations::add ); 406 codeWriter.emitAnnotations( annotations, false ); 407 codeWriter.emit( "$L", enumName ); 408 if( !m_AnonymousTypeArguments.formatParts().isEmpty() ) 409 { 410 codeWriter.emit( "( " ); 411 codeWriter.emit( m_AnonymousTypeArguments ); 412 codeWriter.emit( " )" ); 413 } 414 415 if( getFieldSpecs().isEmpty() && getMethodSpecs().isEmpty() && innerClasses().isEmpty() ) 416 { 417 //---* Avoid unnecessary braces "{}" *------------------------- 418 return; 419 } 420 421 codeWriter.emit( 422 """ 423 424 { 425 """ ); 426 } 427 else if( nonNull( m_AnonymousTypeArguments ) ) 428 { 429 //---* Emit an anonymous type that is not an enum constant *------- 430 final var supertype = getSuperInterfaces().isEmpty() ? getSuperClass() : getSuperInterfaces().getFirst(); 431 432 codeWriter.emit( "new $T( ", supertype ); 433 codeWriter.emit( m_AnonymousTypeArguments ); 434 codeWriter.emit( """ 435 ) 436 { 437 """ ); 438 } 439 else 440 { 441 /* 442 * Push an empty type (specifically without nested types) for 443 * type-resolution. 444 */ 445 codeWriter.pushType( createCopy() ); 446 447 codeWriter.emitJavadoc( getJavadoc() ); 448 final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() ); 449 getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() ) 450 .map( a -> (AnnotationSpecImpl) a ) 451 .ifPresent( annotations::add ); 452 codeWriter.emitAnnotations( annotations, false ); 453 codeWriter.emitModifiers( modifiers(), union( implicitModifiers, CLASS.asMemberModifiers() ) ); 454 codeWriter.emit( "$L $L", CLASS.name().toLowerCase( ROOT ), name().get() ); 455 codeWriter.emitTypeVariables( getTypeVariables() ); 456 457 final List<TypeNameImpl> extendsTypes = getSuperClass().equals( OBJECT ) ? List.of() : List.of( getSuperClass() ); 458 final var implementsTypes = getSuperInterfaces(); 459 460 if( !extendsTypes.isEmpty() ) 461 { 462 codeWriter.emit( " extends" ); 463 var firstType = true; 464 for( final var type : extendsTypes ) 465 { 466 if( !firstType ) codeWriter.emit( "," ); 467 codeWriter.emit( " $T", type ); 468 firstType = false; 469 } 470 } 471 472 if( !implementsTypes.isEmpty() ) 473 { 474 codeWriter.emit( " implements" ); 475 var firstType = true; 476 for( final var type : implementsTypes ) 477 { 478 if( !firstType ) codeWriter.emit( "," ); 479 codeWriter.emit( " $T", type ); 480 firstType = false; 481 } 482 } 483 484 codeWriter.popType(); 485 486 codeWriter.emit( 487 """ 488 489 { 490 """ ); 491 } 492 493 codeWriter.pushType( this ); 494 495 //---* Emit the class body *------------------------------------------- 496 codeWriter.indent(); 497 var firstMember = true; 498 499 //---* Emit the inner types *------------------------------------------ 500 if( !innerClasses().isEmpty() ) 501 { 502 if( !firstMember ) codeWriter.emit( "\n" ); 503 codeWriter.emit( 504 """ 505 /*---------------*\\ 506 ====** Inner Classes **==================================================== 507 \\*---------------*/""" ); 508 innerClasses().stream() 509 .sorted( comparing( t -> t.name().get(), CASE_INSENSITIVE_ORDER ) ) 510 .map( t -> (TypeSpecImpl) t) 511 .forEachOrdered( t -> 512 { 513 codeWriter.emit( "\n" ); 514 t.emit( codeWriter, null, CLASS.implicitTypeModifiers() ); 515 } ); 516 firstMember = false; 517 } 518 519 //--- Constants and attributes *--------------------------------------- 520 if( !getFieldSpecs().isEmpty() ) 521 { 522 final Collection<FieldSpecImpl> alreadyHandled = new HashSet<>(); 523 524 //---* Emit the constants *---------------------------------------- 525 final var constants = getFieldSpecs().stream() 526 .filter( fieldSpec -> fieldSpec.hasModifier( PUBLIC ) ) 527 .filter( fieldSpec -> fieldSpec.hasModifier( STATIC ) ) 528 .filter( fieldSpec -> fieldSpec.hasModifier( FINAL ) ) 529 .filter( FieldSpecImpl::hasInitializer ) 530 .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) ) 531 .toList(); 532 533 if( !constants.isEmpty() ) 534 { 535 if( !firstMember ) codeWriter.emit( "\n" ); 536 codeWriter.emit( 537 """ 538 /*-----------*\\ 539 ====** Constants **======================================================== 540 \\*-----------*/""" ); 541 constants.forEach( constantSpec -> 542 { 543 codeWriter.emit( "\n" ); 544 constantSpec.emit( codeWriter, CLASS.implicitFieldModifiers() ); 545 alreadyHandled.add( constantSpec ); 546 } ); 547 firstMember = false; 548 } 549 550 //---* Emit the attributes *--------------------------------------- 551 final var attributes = getFieldSpecs().stream() 552 .filter( fieldSpec -> !alreadyHandled.contains( fieldSpec ) ) 553 .filter( fieldSpec -> !(fieldSpec.hasModifier( STATIC ) && fieldSpec.hasModifier( FINAL )) ) 554 .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) ) 555 .toList(); 556 557 if( !attributes.isEmpty() ) 558 { 559 if( !firstMember ) codeWriter.emit( "\n" ); 560 codeWriter.emit( 561 """ 562 /*------------*\\ 563 ====** Attributes **======================================================= 564 \\*------------*/""" ); 565 566 attributes.forEach( a -> 567 { 568 codeWriter.emit( "\n" ); 569 a.emit( codeWriter, CLASS.implicitFieldModifiers() ); 570 alreadyHandled.add( a ); 571 } ); 572 firstMember = false; 573 } 574 575 //---* Static fields *--------------------------------------------- 576 final var statics = getFieldSpecs().stream() 577 .filter( staticSpec -> !alreadyHandled.contains( staticSpec ) ) 578 .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) ) 579 .toList(); 580 if( !statics.isEmpty() || !getStaticBlock().isEmpty() ) 581 { 582 if( !firstMember ) codeWriter.emit( "\n" ); 583 codeWriter.emit( 584 """ 585 /*------------------------*\\ 586 ====** Static Initialisations **=========================================== 587 \\*------------------------*/""" ); 588 589 statics.forEach( staticSpec -> 590 { 591 codeWriter.emit( "\n" ); 592 staticSpec.emit( codeWriter, CLASS.implicitFieldModifiers() ); 593 alreadyHandled.add( staticSpec ); 594 } ); 595 596 //---* Static Block *------------------------------------------ 597 if( !getStaticBlock().isEmpty() ) 598 { 599 codeWriter.emit( "\n" ); 600 codeWriter.emit( getStaticBlock() ); 601 } 602 firstMember = false; 603 } 604 } 605 606 //---* Constructors *-------------------------------------------------- 607 final var constructors = getMethodSpecs().stream() 608 .filter( MethodSpecImpl::isConstructor ) 609 .sorted( comparing( MethodSpecImpl::toString, CASE_INSENSITIVE_ORDER ) ) 610 .toList(); 611 if( !getInitializerBlock().isEmpty() || !constructors.isEmpty() ) 612 { 613 if( !firstMember ) codeWriter.emit( "\n" ); 614 codeWriter.emit( 615 """ 616 /*--------------*\\ 617 ====** Constructors **===================================================== 618 \\*--------------*/""" ); 619 620 //---* Initializer block *----------------------------------------- 621 if( !getInitializerBlock().isEmpty() ) 622 { 623 codeWriter.emit( "\n" ); 624 codeWriter.emit( getInitializerBlock() ); 625 } 626 627 //---* Emit the constructors *------------------------------------- 628 constructors.forEach( constructorSpec -> 629 { 630 codeWriter.emit( "\n" ); 631 constructorSpec.emit( codeWriter, name(), CLASS.implicitMethodModifiers() ); 632 } ); 633 firstMember = false; 634 } 635 636 //---* Methods (static and non-static) *------------------------------- 637 final var methods = getMethodSpecs().stream() 638 .filter( methodSpec -> !methodSpec.isConstructor() ) 639 .sorted( TypeSpecImpl::compareMethodSpecs ) 640 .toList(); 641 if( !methods.isEmpty() ) 642 { 643 if( !firstMember ) codeWriter.emit( "\n" ); 644 codeWriter.emit( 645 """ 646 /*---------*\\ 647 ====** Methods **========================================================== 648 \\*---------*/""" ); 649 650 methods.forEach( methodSpec -> 651 { 652 codeWriter.emit( "\n" ); 653 methodSpec.emit( codeWriter, name(), CLASS.implicitMethodModifiers() ); 654 } ); 655 } 656 657 codeWriter.unindent(); 658 codeWriter.popType(); 659 660 codeWriter.emit( "}" ); 661 if( isEmpty( enumName ) && isNull( m_AnonymousTypeArguments ) ) 662 { 663 codeWriter.emit( "\n// $L $N", CLASS.name().toLowerCase( ROOT ), this ); 664 665 /* 666 * If this type isn't also a value, include a trailing newline. 667 */ 668 codeWriter.emit( "\n" ); 669 } 670 } // emit4Foundation() 671 672 /** 673 * Emits the type to the given code writer, using the layout as defined 674 * by the original JavaPoet code. 675 * 676 * @param codeWriter The target code writer. 677 * @param enumName The name of the enum; can be {@code null}. 678 * @param implicitModifiers The implicit modifiers. 679 * @throws UncheckedIOException A problem occurred when writing to the 680 * output target. 681 */ 682 @SuppressWarnings( {"BoundedWildcard", "OptionalGetWithoutIsPresent", "MethodWithMultipleReturnPoints", "OverlyLongMethod", "OverlyComplexMethod"} ) 683 @Override 684 protected final void emit4JavaPoet( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter, final String enumName, final Set<Modifier> implicitModifiers ) throws UncheckedIOException 685 { 686 if( isNotEmptyOrBlank( enumName ) ) 687 { 688 //---* Emit an enum constant *------------------------------------- 689 /* 690 * enum constants are implemented as anonymous types so this is 691 * called from an outer type spec. 692 */ 693 codeWriter.emitJavadoc( getJavadoc() ); 694 final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() ); 695 getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() ) 696 .map( a -> (AnnotationSpecImpl) a ) 697 .ifPresent( annotations::add ); 698 codeWriter.emitAnnotations( annotations, false ); 699 codeWriter.emit( "$L", enumName ); 700 if( !m_AnonymousTypeArguments.formatParts().isEmpty() ) 701 { 702 codeWriter.emit( "(" ); 703 codeWriter.emit( m_AnonymousTypeArguments ); 704 codeWriter.emit( ")" ); 705 } 706 707 if( getFieldSpecs().isEmpty() && getMethodSpecs().isEmpty() && innerClasses().isEmpty() ) 708 { 709 //---* Avoid unnecessary braces "{}" *------------------------- 710 return; 711 } 712 713 codeWriter.emit( " {\n" ); 714 } 715 else if( nonNull( m_AnonymousTypeArguments ) ) 716 { 717 //---* Emit an anonymous type that is not an enum constant *------- 718 final var supertype = getSuperInterfaces().isEmpty() ? getSuperClass() : getSuperInterfaces().getFirst(); 719 720 codeWriter.emit( "new $T(", supertype ); 721 codeWriter.emit( m_AnonymousTypeArguments ); 722 codeWriter.emit( ") {\n" ); 723 } 724 else 725 { 726 /* 727 * Push an empty type (specifically without nested types) for 728 * type-resolution. 729 */ 730 codeWriter.pushType( createCopy() ); 731 732 codeWriter.emitJavadoc( getJavadoc() ); 733 final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() ); 734 getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() ) 735 .map( a -> (AnnotationSpecImpl) a ) 736 .ifPresent( annotations::add ); 737 codeWriter.emitAnnotations( annotations, false ); 738 codeWriter.emitModifiers( modifiers(), union( implicitModifiers, CLASS.asMemberModifiers() ) ); 739 codeWriter.emit( "$L $L", CLASS.name().toLowerCase( ROOT ), name().get() ); 740 codeWriter.emitTypeVariables( getTypeVariables() ); 741 742 final List<TypeNameImpl> extendsTypes = getSuperClass().equals( OBJECT ) ? List.of() : List.of( getSuperClass() ); 743 final var implementsTypes = getSuperInterfaces(); 744 745 if( !extendsTypes.isEmpty() ) 746 { 747 codeWriter.emit( " extends" ); 748 var firstType = true; 749 for( final var type : extendsTypes ) 750 { 751 if( !firstType ) codeWriter.emit( "," ); 752 codeWriter.emit( " $T", type ); 753 firstType = false; 754 } 755 } 756 757 if( !implementsTypes.isEmpty() ) 758 { 759 codeWriter.emit( " implements" ); 760 var firstType = true; 761 for( final var type : implementsTypes ) 762 { 763 if( !firstType ) codeWriter.emit( "," ); 764 codeWriter.emit( " $T", type ); 765 firstType = false; 766 } 767 } 768 769 codeWriter.popType(); 770 771 codeWriter.emit( " {\n" ); 772 } 773 774 codeWriter.pushType( this ); 775 776 //---* Emit the class body *------------------------------------------- 777 codeWriter.indent(); 778 var firstMember = true; 779 780 //---* Static fields *------------------------------------------------- 781 for( final var fieldSpec : getFieldSpecs() ) 782 { 783 if( !fieldSpec.hasModifier( STATIC ) ) continue; 784 if( !firstMember ) codeWriter.emit( "\n" ); 785 fieldSpec.emit( codeWriter, CLASS.implicitFieldModifiers() ); 786 firstMember = false; 787 } 788 789 if( !getStaticBlock().isEmpty() ) 790 { 791 if( !firstMember ) codeWriter.emit( "\n" ); 792 codeWriter.emit( getStaticBlock() ); 793 firstMember = false; 794 } 795 796 //--* Non-static fields *---------------------------------------------- 797 for( final var fieldSpec : getFieldSpecs() ) 798 { 799 if( fieldSpec.hasModifier( STATIC ) ) continue; 800 if( !firstMember ) codeWriter.emit( "\n" ); 801 fieldSpec.emit( codeWriter, CLASS.implicitFieldModifiers() ); 802 firstMember = false; 803 } 804 805 //---* Initializer block *--------------------------------------------- 806 if( !getInitializerBlock().isEmpty() ) 807 { 808 if( !firstMember ) codeWriter.emit( "\n" ); 809 codeWriter.emit( getInitializerBlock() ); 810 firstMember = false; 811 } 812 813 //---* Constructors *-------------------------------------------------- 814 for( final var methodSpec : getMethodSpecs() ) 815 { 816 if( !methodSpec.isConstructor() ) continue; 817 if( !firstMember ) codeWriter.emit( "\n" ); 818 methodSpec.emit( codeWriter, name(), CLASS.implicitMethodModifiers() ); 819 firstMember = false; 820 } 821 822 //---* Methods (static and non-static) *------------------------------- 823 for( final var methodSpec : getMethodSpecs() ) 824 { 825 if( methodSpec.isConstructor() ) continue; 826 if( !firstMember ) codeWriter.emit( "\n" ); 827 methodSpec.emit( codeWriter, name(), CLASS.implicitMethodModifiers() ); 828 firstMember = false; 829 } 830 831 //---* Types (inner classes) *----------------------------------------- 832 for( final var typeSpec : innerClasses() ) 833 { 834 if( !firstMember ) codeWriter.emit( "\n" ); 835 ((TypeSpecImpl) typeSpec).emit( codeWriter, null, CLASS.implicitTypeModifiers() ); 836 firstMember = false; 837 } 838 839 codeWriter.unindent(); 840 codeWriter.popType(); 841 842 codeWriter.emit( "}" ); 843 if( isEmpty( enumName ) && isNull( m_AnonymousTypeArguments ) ) 844 { 845 /* 846 * If this type isn't also a value, include a trailing newline. 847 */ 848 codeWriter.emit( "\n" ); 849 } 850 } // emit4JavaPoet() 851 852 /** 853 * {@inheritDoc} 854 */ 855 @Override 856 public final boolean equals( final Object o ) 857 { 858 var retValue = this == o; 859 if( !retValue && (o instanceof final ClassSpecImpl other) ) 860 { 861 retValue = getFactory().equals( other.getFactory() ) && toString().equals( o.toString() ); 862 } 863 864 //---* Done *---------------------------------------------------------- 865 return retValue; 866 } // equals() 867 868 /** 869 * {@inheritDoc} 870 */ 871 @Override 872 public final int hashCode() { return hash( getFactory(), toString() ); } 873 874 /** 875 * {@inheritDoc} 876 */ 877 @Override 878 public final TypeSpecImpl.BuilderImpl toBuilder() 879 { 880 final var retValue = new BuilderImpl( getFactory(), name(), (CodeBlockImpl) anonymousTypeArguments().orElse( null ) ); 881 retValue.getJavadoc().addWithoutDebugInfo( getJavadoc() ); 882 retValue.getAnnotations().addAll( getAnnotations() ); 883 retValue.getModifiers().addAll( modifiers() ); 884 retValue.getTypeVariables().addAll( getTypeVariables() ); 885 retValue.superclass( getSuperClass() ); 886 retValue.getSuperinterfaces().addAll( getSuperInterfaces() ); 887 retValue.getFieldSpecs().addAll( getFieldSpecs() ); 888 retValue.getMethodSpecs().addAll( getMethodSpecs() ); 889 retValue.getTypeSpecs().addAll( typeSpecs() ); 890 retValue.getInitializerBlock().addWithoutDebugInfo( getInitializerBlock() ); 891 retValue.getStaticBlock().addWithoutDebugInfo( getStaticBlock() ); 892 retValue.getStaticImports().addAll( getStaticImports() ); 893 retValue.addSuppressableWarning( getSuppressableWarnings().toArray( SuppressableWarnings []::new ) ); 894 895 //---* Done *---------------------------------------------------------- 896 return retValue; 897 } // toBuilder() 898} 899// class ClassSpecImpl 900 901/* 902 * End of File 903 */