001/* 002 * ============================================================================ 003 * Copyright © 2015 Square, Inc. 004 * Copyright for the modifications © 2018-2021 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.TypeSpecImpl.Kind.INTERFACE; 035import static org.tquadrat.foundation.javacomposer.internal.Util.createDebugOutput; 036import static org.tquadrat.foundation.javacomposer.internal.Util.requireExactlyOneOf; 037import static org.tquadrat.foundation.javacomposer.internal.Util.union; 038import static org.tquadrat.foundation.lang.Objects.checkState; 039import static org.tquadrat.foundation.lang.Objects.hash; 040import static org.tquadrat.foundation.lang.Objects.isNull; 041import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 042import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 043 044import javax.lang.model.element.Modifier; 045import java.io.UncheckedIOException; 046import java.util.ArrayList; 047import java.util.Collection; 048import java.util.EnumSet; 049import java.util.HashSet; 050import java.util.Optional; 051import java.util.Set; 052 053import org.apiguardian.api.API; 054import org.tquadrat.foundation.annotation.ClassVersion; 055import org.tquadrat.foundation.exception.ValidationException; 056import org.tquadrat.foundation.javacomposer.CodeBlock; 057import org.tquadrat.foundation.javacomposer.FieldSpec; 058import org.tquadrat.foundation.javacomposer.JavaComposer; 059import org.tquadrat.foundation.javacomposer.MethodSpec; 060import org.tquadrat.foundation.javacomposer.SuppressableWarnings; 061import org.tquadrat.foundation.javacomposer.TypeName; 062import org.tquadrat.foundation.javacomposer.TypeSpec; 063 064/** 065 * The implementation of 066 * {@link TypeSpec} 067 * for an interface. 068 * 069 * @author Square,Inc. 070 * @modified Thomas Thrien - thomas.thrien@tquadrat.org 071 * @version $Id: InterfaceSpecImpl.java 1064 2023-09-26 20:16:12Z tquadrat $ 072 * @since 0.2.0 073 * 074 * @UMLGraph.link 075 */ 076@ClassVersion( sourceVersion = "$Id: InterfaceSpecImpl.java 1064 2023-09-26 20:16:12Z tquadrat $" ) 077@API( status = INTERNAL, since = "0.2.0" ) 078public final class InterfaceSpecImpl extends TypeSpecImpl 079{ 080 /*---------------*\ 081 ====** Inner Classes **==================================================== 082 \*---------------*/ 083 /** 084 * The implementation of 085 * {@link Builder} 086 * for an interface. 087 * 088 * @author Square,Inc. 089 * @modified Thomas Thrien - thomas.thrien@tquadrat.org 090 * @version $Id: InterfaceSpecImpl.java 1064 2023-09-26 20:16:12Z tquadrat $ 091 * @since 0.2.0 092 * 093 * @UMLGraph.link 094 */ 095 @ClassVersion( sourceVersion = "$Id: InterfaceSpecImpl.java 1064 2023-09-26 20:16:12Z tquadrat $" ) 096 @API( status = INTERNAL, since = "0.2.0" ) 097 public static final class BuilderImpl extends TypeSpecImpl.BuilderImpl 098 { 099 /*--------------*\ 100 ====** Constructors **================================================= 101 \*--------------*/ 102 /** 103 * Creates a new {@code BuilderImpl} instance. 104 * 105 * @param composer The reference to the factory that created this 106 * builder instance. 107 * @param name The name of the type to build. 108 */ 109 public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer, final CharSequence name ) 110 { 111 this( composer, Optional.of( requireNotEmptyArgument( name, "name" ).toString() ) ); 112 } // BuilderImpl() 113 114 /** 115 * Creates a new {@code BuilderImpl} instance. 116 * 117 * @param composer The reference to the factory that created this 118 * builder instance. 119 * @param name The name of the type to build. 120 */ 121 @SuppressWarnings( {"OptionalUsedAsFieldOrParameterType"} ) 122 public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer, final Optional<String> name ) 123 { 124 super( composer, INTERFACE, name ); 125 } // BuilderImpl() 126 127 /*---------*\ 128 ====** Methods **====================================================== 129 \*---------*/ 130 /** 131 * {@inheritDoc} 132 */ 133 @API( status = STABLE, since = "0.2.0" ) 134 @Override 135 public final BuilderImpl addAttribute( final FieldSpec fieldSpec, final boolean readOnly ) 136 { 137 throw new ValidationException( "Attributes are not allowed for interface %s".formatted( getName() .orElse( NAME_ANONYMOUS_TYPE ) ) ); 138 } // addAttribute() 139 140 /** 141 * {@inheritDoc} 142 */ 143 @Override 144 public final BuilderImpl addField( final FieldSpec fieldSpec ) 145 { 146 final var fieldSpecImpl = (FieldSpecImpl) requireNonNullArgument( fieldSpec, "fieldSpec" ); 147 final var modifiers = fieldSpecImpl.modifiers(); 148 requireExactlyOneOf( modifiers, PUBLIC, PRIVATE ); 149 final Set<Modifier> check = EnumSet.of( STATIC, FINAL ); 150 checkState( modifiers.containsAll( check ), () -> new ValidationException( "%s %s.%s requires modifiers %s".formatted( INTERFACE, getName().orElse( NAME_ANONYMOUS_TYPE ), fieldSpec.name(), check ) ) ); 151 super.addField( fieldSpecImpl ); 152 153 //---* Done *------------------------------------------------------ 154 return this; 155 } // addField() 156 157 /** 158 * {@inheritDoc} 159 */ 160 @Override 161 public final BuilderImpl addInitializerBlock( final CodeBlock block ) 162 { 163 throw new UnsupportedOperationException( INTERFACE + " can't have initializer blocks" ); 164 } // addInitializerBlock() 165 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public final BuilderImpl addMethod( final MethodSpec methodSpec ) 171 { 172 final var methodSpecImpl = (MethodSpecImpl) requireNonNullArgument( methodSpec, "methodSpec" ); 173 final var modifiers = methodSpecImpl.modifiers(); 174 requireExactlyOneOf( modifiers, ABSTRACT, STATIC, DEFAULT ); 175 requireExactlyOneOf( modifiers, PUBLIC, PRIVATE); 176 checkState( methodSpecImpl.defaultValue().isEmpty(), () -> new IllegalStateException( "%s %s.%s cannot have a default value".formatted( INTERFACE, getName().orElse( NAME_ANONYMOUS_TYPE ), methodSpecImpl.name() ) ) ); 177 final var methodSpecs = getMethodSpecs(); 178 if( composer().addDebugOutput() ) 179 { 180 final var builder = methodSpecImpl.toBuilder( false ); 181 createDebugOutput( true ).ifPresent( debug -> builder.addJavadoc( "\n$L\n", debug.asLiteral() ) ); 182 methodSpecs.add( builder.build() ); 183 } 184 else 185 { 186 methodSpecs.add( methodSpecImpl ); 187 } 188 189 final var maxMethods = composer().getMaxMethods(); 190 if( (maxMethods > 0) && (methodSpecs.size() >= maxMethods) ) 191 { 192 addSuppressableWarning( CLASS_WITH_TOO_MANY_METHODS ); 193 } 194 195 //---* Done *------------------------------------------------------ 196 return this; 197 } // addMethod() 198 199 /** 200 * {@inheritDoc} 201 */ 202 @Override 203 public final Builder addProperty( final FieldSpec fieldSpec, final boolean readOnly ) 204 { 205 throw new ValidationException( "Properties are not allowed for interface %s".formatted( getName().orElse( NAME_ANONYMOUS_TYPE ) ) ); 206 } // addProperty() 207 208 /** 209 * {@inheritDoc} 210 */ 211 @Override 212 public final InterfaceSpecImpl build() 213 { 214 final var retValue = new InterfaceSpecImpl( this ); 215 216 //---* Done *------------------------------------------------------ 217 return retValue; 218 } // build() 219 220 /** 221 * {@inheritDoc} 222 */ 223 @Override 224 public final BuilderImpl superclass( final TypeName superclass ) 225 { 226 throw new IllegalStateException( "only classes have super classes, not " + INTERFACE ); 227 } // superclass() 228 } 229 // class BuilderImpl 230 231 /*--------------*\ 232 ====** Constructors **===================================================== 233 \*--------------*/ 234 /** 235 * Creates a new {@code InterfaceSpecImpl} instance. 236 * 237 * @param builder The builder for this instance. 238 */ 239 public InterfaceSpecImpl( @SuppressWarnings( "UseOfConcreteClass" ) final BuilderImpl builder ) 240 { 241 super( builder ); 242 } // InterfaceSpecImpl() 243 244 /** 245 * Creates a dummy type spec for type-resolution in CodeWriter only while 246 * emitting the type declaration but before entering the type body. 247 * 248 * @param type The source type. 249 */ 250 private InterfaceSpecImpl( @SuppressWarnings( "UseOfConcreteClass" ) final InterfaceSpecImpl type ) 251 { 252 super( type ); 253 } // InterfaceSpecImpl() 254 255 /*---------*\ 256 ====** Methods **========================================================== 257 \*---------*/ 258 /** 259 * {@inheritDoc} 260 */ 261 @Override 262 protected final TypeSpecImpl createCopy() { return new InterfaceSpecImpl( this ); } 263 264 /** 265 * Emits the type to the given code writer, using the layout as defined 266 * by the Foundation library code. 267 * 268 * @param codeWriter The target code writer. 269 * @param enumName The name of the enum; can be {@code null}. 270 * @param implicitModifiers The implicit modifiers. 271 * @throws UncheckedIOException A problem occurred when writing to the 272 * output target. 273 */ 274 @SuppressWarnings( {"BoundedWildcard", "OptionalGetWithoutIsPresent", "OverlyLongMethod", "OverlyComplexMethod"} ) 275 @Override 276 protected final void emit4Foundation( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter, final String enumName, final Set<Modifier> implicitModifiers ) throws UncheckedIOException 277 { 278 assert isNull( enumName ) : "enumName has to be null"; 279 280 /* 281 * Push an empty type (specifically without nested types) for 282 * type-resolution. 283 */ 284 codeWriter.pushType( createCopy() ); 285 286 codeWriter.emitJavadoc( getJavadoc() ); 287 final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() ); 288 getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() ) 289 .map( a -> (AnnotationSpecImpl) a ) 290 .ifPresent( annotations::add ); 291 codeWriter.emitAnnotations( annotations, false ); 292 codeWriter.emitModifiers( modifiers(), union( implicitModifiers, INTERFACE.asMemberModifiers() ) ); 293 codeWriter.emit( "$L $L", INTERFACE.name().toLowerCase( ROOT ), name().get() ); 294 codeWriter.emitTypeVariables( getTypeVariables() ); 295 296 final var extendsTypes = getSuperInterfaces(); 297 if( !extendsTypes.isEmpty() ) 298 { 299 codeWriter.emit( " extends" ); 300 var firstType = true; 301 for( final var type : extendsTypes ) 302 { 303 if( !firstType ) codeWriter.emit( "," ); 304 codeWriter.emit( " $T", type ); 305 firstType = false; 306 } 307 } 308 309 codeWriter.popType(); 310 311 codeWriter.emit( "\n{\n" ); 312 313 codeWriter.pushType( this ); 314 315 //---* Emit the class body *------------------------------------------- 316 codeWriter.indent(); 317 var firstMember = true; 318 319 //---* Emit the inner types *------------------------------------------ 320 if( !innerClasses().isEmpty() ) 321 { 322 if( !firstMember ) codeWriter.emit( "\n" ); 323 codeWriter.emit( 324 """ 325 /*---------------*\\ 326 ====** Inner Classes **==================================================== 327 \\*---------------*/""" ); 328 innerClasses().stream() 329 .sorted( comparing( t -> t.name().get(), CASE_INSENSITIVE_ORDER ) ) 330 .map( t -> (TypeSpecImpl) t ) 331 .forEachOrdered( t -> 332 { 333 codeWriter.emit( "\n" ); 334 t.emit( codeWriter, null, INTERFACE.implicitTypeModifiers() ); 335 } ); 336 firstMember = false; 337 } 338 339 //--- Constants and attributes *--------------------------------------- 340 if( !getFieldSpecs().isEmpty() ) 341 { 342 final Collection<FieldSpecImpl> alreadyHandled = new HashSet<>(); 343 344 //---* Emit the constants *---------------------------------------- 345 final var constants = getFieldSpecs().stream() 346 .filter( constantSpec -> constantSpec.hasModifier( PUBLIC ) ) 347 .filter( constantSpec -> constantSpec.hasModifier( STATIC ) ) 348 .filter( constantSpec -> constantSpec.hasModifier( FINAL ) ) 349 .filter( FieldSpecImpl::hasInitializer ) 350 .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) ) 351 .toList(); 352 353 if( !constants.isEmpty() ) 354 { 355 if( !firstMember ) codeWriter.emit( "\n" ); 356 codeWriter.emit( 357 """ 358 /*-----------*\\ 359 ====** Constants **======================================================== 360 \\*-----------*/""" ); 361 constants.forEach( constantSpec -> 362 { 363 codeWriter.emit( "\n" ); 364 constantSpec.emit( codeWriter, INTERFACE.implicitFieldModifiers() ); 365 alreadyHandled.add( constantSpec ); 366 } ); 367 firstMember = false; 368 } 369 370 //---* Emit the attributes *--------------------------------------- 371 final var attributes = getFieldSpecs().stream() 372 .filter( fieldSpec -> !alreadyHandled.contains( fieldSpec ) ) 373 .filter( fieldSpec -> !(fieldSpec.hasModifier( STATIC ) && fieldSpec.hasModifier( FINAL )) ) 374 .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) ) 375 .toList(); 376 377 if( !attributes.isEmpty() ) 378 { 379 if( !firstMember ) codeWriter.emit( "\n" ); 380 codeWriter.emit( 381 """ 382 /*------------*\\ 383 ====** Attributes **======================================================= 384 \\*------------*/""" ); 385 386 attributes.forEach( a -> 387 { 388 codeWriter.emit( "\n" ); 389 a.emit( codeWriter, INTERFACE.implicitFieldModifiers() ); 390 alreadyHandled.add( a ); 391 } ); 392 firstMember = false; 393 } 394 395 //---* Static fields *--------------------------------------------- 396 final var statics = getFieldSpecs().stream() 397 .filter( staticSpec -> !alreadyHandled.contains( staticSpec ) ) 398 .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) ) 399 .toList(); 400 if( !statics.isEmpty() || !getStaticBlock().isEmpty() ) 401 { 402 if( !firstMember ) codeWriter.emit( "\n" ); 403 codeWriter.emit( 404 """ 405 /*------------------------*\\ 406 ====** Static Initialisations **=========================================== 407 \\*------------------------*/""" ); 408 409 statics.forEach( staticSpec -> 410 { 411 codeWriter.emit( "\n" ); 412 staticSpec.emit( codeWriter, INTERFACE.implicitFieldModifiers() ); 413 alreadyHandled.add( staticSpec ); 414 } ); 415 416 //---* Static Block *------------------------------------------ 417 if( !getStaticBlock().isEmpty() ) 418 { 419 codeWriter.emit( "\n" ); 420 codeWriter.emit( getStaticBlock() ); 421 } 422 firstMember = false; 423 } 424 } 425 426 //---* Methods (static and non-static) *------------------------------- 427 final var methods = getMethodSpecs().stream() 428 .sorted( TypeSpecImpl::compareMethodSpecs ) 429 .toList(); 430 if( !methods.isEmpty() ) 431 { 432 if( !firstMember ) codeWriter.emit( "\n" ); 433 codeWriter.emit( 434 """ 435 /*---------*\\ 436 ====** Methods **========================================================== 437 \\*---------*/""" ); 438 439 methods.forEach( methodSpec -> 440 { 441 codeWriter.emit( "\n" ); 442 methodSpec.emit( codeWriter, name(), INTERFACE.implicitMethodModifiers() ); 443 } ); 444 } 445 446 codeWriter.unindent(); 447 codeWriter.popType(); 448 449 codeWriter.emit( 450 """ 451 } 452 // $L $N 453 """, INTERFACE.name().toLowerCase( ROOT ), this ); 454 } // emit4Foundation() 455 456 /** 457 * Emits the type to the given code writer, using the layout as defined 458 * by the original JavaPoet code. 459 * 460 * @param codeWriter The target code writer. 461 * @param enumName The name of the enum; can be {@code null}. 462 * @param implicitModifiers The implicit modifiers. 463 * @throws UncheckedIOException A problem occurred when writing to the 464 * output target. 465 */ 466 @SuppressWarnings( {"BoundedWildcard", "OptionalGetWithoutIsPresent", "OverlyComplexMethod"} ) 467 @Override 468 protected final void emit4JavaPoet( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter, final String enumName, final Set<Modifier> implicitModifiers ) throws UncheckedIOException 469 { 470 assert isNull( enumName ) : "enumName has to be null"; 471 472 /* 473 * Push an empty type (specifically without nested types) for 474 * type-resolution. 475 */ 476 codeWriter.pushType( createCopy() ); 477 478 codeWriter.emitJavadoc( getJavadoc() ); 479 final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() ); 480 getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() ) 481 .map( a -> (AnnotationSpecImpl) a ) 482 .ifPresent( annotations::add ); 483 codeWriter.emitAnnotations( annotations, false ); 484 codeWriter.emitModifiers( modifiers(), union( implicitModifiers, INTERFACE.asMemberModifiers() ) ); 485 codeWriter.emit( "$L $L", INTERFACE.name().toLowerCase( ROOT ), name().get() ); 486 codeWriter.emitTypeVariables( getTypeVariables() ); 487 488 final var extendsTypes = getSuperInterfaces(); 489 if( !extendsTypes.isEmpty() ) 490 { 491 codeWriter.emit( " extends" ); 492 var firstType = true; 493 for( final var type : extendsTypes ) 494 { 495 if( !firstType ) codeWriter.emit( "," ); 496 codeWriter.emit( " $T", type ); 497 firstType = false; 498 } 499 } 500 501 codeWriter.popType(); 502 503 codeWriter.emit( " {\n" ); 504 505 codeWriter.pushType( this ); 506 507 //---* Emit the class body *------------------------------------------- 508 codeWriter.indent(); 509 var firstMember = true; 510 511 //---* Static fields *------------------------------------------------- 512 for( final var fieldSpec : getFieldSpecs() ) 513 { 514 if( !firstMember ) codeWriter.emit( "\n" ); 515 fieldSpec.emit( codeWriter, INTERFACE.implicitFieldModifiers() ); 516 firstMember = false; 517 } 518 519 if( !getStaticBlock().isEmpty() ) 520 { 521 if( !firstMember ) codeWriter.emit( "\n" ); 522 codeWriter.emit( getStaticBlock() ); 523 firstMember = false; 524 } 525 526 //---* Methods (static and non-static) *------------------------------- 527 for( final var methodSpec : getMethodSpecs() ) 528 { 529 if( !firstMember ) codeWriter.emit( "\n" ); 530 methodSpec.emit( codeWriter, name(), INTERFACE.implicitMethodModifiers() ); 531 firstMember = false; 532 } 533 534 //---* Types (inner classes) *----------------------------------------- 535 for( final var typeSpec : innerClasses() ) 536 { 537 if( !firstMember ) codeWriter.emit( "\n" ); 538 ((TypeSpecImpl) typeSpec).emit( codeWriter, null, INTERFACE.implicitTypeModifiers() ); 539 firstMember = false; 540 } 541 542 codeWriter.unindent(); 543 codeWriter.popType(); 544 545 codeWriter.emit( 546 """ 547 } 548 """ ); 549 } // emit4JavaPoet() 550 551 /** 552 * {@inheritDoc} 553 */ 554 @Override 555 public final boolean equals( final Object o ) 556 { 557 var retValue = this == o; 558 if( !retValue && (o instanceof final InterfaceSpecImpl other) ) 559 { 560 retValue = getFactory().equals( other.getFactory() ) && toString().equals( o.toString() ); 561 } 562 563 //---* Done *---------------------------------------------------------- 564 return retValue; 565 } // equals() 566 567 /** 568 * {@inheritDoc} 569 */ 570 @Override 571 public final int hashCode() { return hash( getFactory(), toString() ); } 572 573 /** 574 * {@inheritDoc} 575 */ 576 @Override 577 public final BuilderImpl toBuilder() 578 { 579 final var retValue = new BuilderImpl( getFactory(), name() ); 580 retValue.getJavadoc().addWithoutDebugInfo( getJavadoc() ); 581 retValue.getAnnotations().addAll( getAnnotations() ); 582 retValue.getModifiers().addAll( modifiers() ); 583 retValue.getTypeVariables().addAll( getTypeVariables() ); 584 retValue.getSuperinterfaces().addAll( getSuperInterfaces() ); 585 retValue.getFieldSpecs().addAll( getFieldSpecs() ); 586 retValue.getMethodSpecs().addAll( getMethodSpecs() ); 587 retValue.getTypeSpecs().addAll( typeSpecs() ); 588 retValue.getInitializerBlock().addWithoutDebugInfo( getInitializerBlock() ); 589 retValue.getStaticBlock().addWithoutDebugInfo( getStaticBlock() ); 590 retValue.getStaticImports().addAll( getStaticImports() ); 591 retValue.addSuppressableWarning( getSuppressableWarnings().toArray( SuppressableWarnings[]::new ) ); 592 593 //---* Done *---------------------------------------------------------- 594 return retValue; 595 } // toBuilder() 596} 597// class InterfaceSpecImpl 598 599/* 600 * End of File 601 */